Photo by Ag PIC / Unsplash

Tiny Tool: checkRefs.sh

tiny tool Jul 16, 2025

Addendum: why not xargs?

Why not, indeed? I revisited this tiny tool as I built a short workshop on xargs and rethought (again, with an LLM). This one-liner is the end product:

ls -1 ./templates | xargs -I{} sh -c 'echo "## {}"; grep -Flsir "{}" ./* ' 

That substitutes the shell script below for

xargs -I{} sh -c 'echo "## {}"; grep -Flsir "{}" ./* '

What does this do?

  • ls -1 ./templates makes a list of the filenames in the templates directory
  • | sends the list to xargs
  • xargs -I{} sh -c works through that list of filenames, runs the shell script in the single quotes '...', and substitutes any {} in the shell script for the filename.
  • echo "## {}"; Sends the filename prefixed with ## to the terminal
  • grep -Flsir "{}" ./* ' seeks the filename in any of the files in the current directory, with -Flsi options as below. Note the new r recursive option, so it track tracks down the hierarchy, and the starting point and pattern ./. I could use . for the current directory, but this pattern lets me change directories up (../) and down (./target/*). At the moment, this picks up the template directory, too.
while read -r referenced_String; do
    matches=$(grep -Flsi "$referenced_String" *)
    echo "## ${referenced_String}"
    if [ -n "$matches" ]; then
        echo "$matches"
    else
        echo "unreferenced"
    fi
done

Used with ls -1 ./templates | sh ./checkRefs.sh, this will check the files in the current directory to see which ones use a file from templates

To try it out, clone this for an example set of files, the shell script and command. https://github.com/workroomprds/template_lists_example

I used this when building playbooks, to see which templates were used by files in the current directory – and which were unused. If a file referenced two templates, I could see that, too.

I built the tool because I did the work by hand a couple of times, and that became slow and error-prone as the number of files and strings increased. I couldn't find a way to make a tool (either on the commandline or within my IDE) do it. So it's tool time. Making it took about 15 minutes, and taught me some stuff – time well spent. Writing about making it has taken an hour.

I imagine I'll use it generally: I'll certainly use it on my current project again.

Subscribers get to see how I used LLMs Qwen3 and Claude3.7Sonnet.

Building

I built it / breathed it into existence by asking Qwen3 (local, thinking) and Claude3.7 (cloud, powerful, thinking off).

  • Qwen3 ended up with a script close to the one above.
  • Claude3.7 built something more robust, with better output, but a bit harder to read.
  • I took Qwen's, added the "unreferenced" from Claude and a grep option, and fiddled the referenced_String to help me understand next time.

I checked its output against something I'd already done by hand – it matched. To check that a string referenced multiple times turned up several times, and at the same time checking the behaviour for a file referencing several strings (which wasn't going to be checked by the set I was using by hand), I temporarily popped a file listing of the templates directory into the current directory – so every string was in twice, and one file had them all. That took less time to do than to describe, and worked to my satisfaction.

Details of prompting

The prompt I used started with was :

Given a list of strings (i.e. a file of newline-delimited filenames) I want to search through a directory of files to see where those filenames are used, and whether any filename is unused.

I had a couple of conversations with Qwen, which was fixating on seeing what files were in both directories, and got very confused in its thinking (particularly with a list of filenames to search in a list of named files). Those conversations helped me to refine by asking for a shell script, by clarifying the difference between the strings to be searched for and the files to search, and by clarifying the way I'd use it in terms of input and output. Watching Qwen also gave me time to pause and think – but it's a mistake to imagine that one's thinking is improved while watching an LLM chew its own tail.

The eventual conversation starting prompt ended up being:

Given a list of strings (i.e. a file of newline-delimited filenames) I want to search through a directory of files to see which files contain those strings/filenames, and whether any string/filename is unused. I want a shell script, taking stdin as input. I will probably pipi ls -1 to the input. I want the output organised (for now) by the input list in the form ## «string»

For this, Qwen got (literally) lost in thought over several minutes. It eventually output code in its 'think' response, threw up into the conversation title (weird...) and failed to output anything else at all. But the code looked OK.

I ran the same prompt with Claude3.7Sonnet, which gave me 50+ lines of guff. I asked several times to make the offered code terse and readable: by using grep rather than find, by removing robustness, by removing a "count" function, and by removing bullet points in the output. I asked it to compare its eventual output with Qwen's / mine, and it outlined differences that... led me back to the simpler one.

I say "simpler", but the grep -Flsi bit is complex, and more complex than suggested by either. Qwen went for grep -Fls, Claude went even simpler but did suggest -i to make the command case-insensitive (I want to see if something's used, not if it's used well, so that's better). I can imagine that I'll wonder what they meant, and I imagine I'll ask an LLM, so...

Here's Claude to explain:

In bullet points, talk me through the options used in grep -Flsi

  • FFixed strings - Treats the pattern as a fixed string rather than a regular expression. This means special regex characters like .*[, etc. are treated literally.
  • lList filenames only - Instead of showing the matching lines, grep only outputs the names of files containing matches. Each filename is printed once, even if multiple matches exist in the file.
  • sSuppress error messages - Silences error messages about nonexistent or unreadable files. Useful for avoiding error output when grep encounters permission issues or other errors.
  • iCase-insensitive - Makes the search ignore case differences between the pattern and the file contents. For example, searching for "hello" would match "Hello", "HELLO", etc.

Together, these options make grep return just the filenames where the exact string appears (ignoring case), without showing any error messages.

(LLM-generated content, judged reasonable by me)

Tags

James Lyndsay

Getting better at software testing. Singing in Bulgarian. Staying in. Going out. Listening. Talking. Writing. Making.