Posted Monday, 26-Oct-2026 by Ingo Karkat
As one's toolset grows with time, it becomes increasingly difficult to keep things consistent and up-to-date. You learn new tricks, add new development tools, or realize that an old idiom you've used pervasively in the past has a problem. If you don't update all of your projects, things quickly devolve into a mess of inconsistent stuff. It's a real struggle; here are three examples that show how I try to fight back.
26-Sep-2026I've encountered an old-style Bats test case that didn't yet use the bats-assert test library. I must have missed that (likely because it was in my extensions subtree, not the regular bin one)! Fortunately, because I had encoded most of the conversion into a custom :BatsConvert Vim command, doing the conversion, even after so much time, was a breeze. But… are there any other such missed test cases? I quickly1 devised a shell command that would answer that question:
$ Unixhome-ingo --predicate-command "\
negateThis --status-99-is-noop \
eachDir --worst-status --local-dir --command \
'! hasglobmatch \"*.bats\" || test -e fixture.bash' \
-- tests/*/"
/home/inkarkat/Unixhome/bin/ingo/shell-user-interactions
/home/inkarkat/Unixhome/bin/ingo/reldate
/home/inkarkat/Unixhome/lib/ingo/scripting-setup
Unixhome-ingo is an iterator over all of my personal projects; without a passed command, it will simply list the file system locations of all working copies --predicate-command limits the result to those working copies where the command succeeds eachDir --local-dir … -- tests/*/ iterates over all subdirectories in the tests/ subdir of a project; as my projects consist of individual commands, there's a separate test subdirectory for each one --worst-status takes care of the at least one, but for that we have to negate the inner iteration: it succeeds if there are no Bats tests at all, or if a fixture is found negateThis is a custom tool that works like the shell's !, but with --status-99-is-noop also takes into account that eachDir will indicate no existing test subdirs (i.e. when the glob doesn't match) with exit status 99 In the end, the command reported one actual old-style test (reldate) that I had overlooked so far; the other two were false positives (shell-user-interactions has tests that reuse a fixture from an adjacent subdir, and scripting-setup is a demo that intentionally doesn't use the library).
Two more recent uses of Unixhome-ingo iteration:
Another nifty use of Unixhome-ingo iteration: 60+ of my scripts had this cleanup command:
trap '${DEBUG:+debugcommand --} rm -f -- "$filespec" 2>/dev/null' EXIT
This uses my shell-debugging project's debugcommand that allows skipping the cleanup if the DEBUG environment variable contains the right value (so one can inspect the temporary files that would normally be removed when the script ends). For an examplescript, that would be DEBUG=examplescript!; the ! is the extension that causes the skipping:
When $DEBUG refers to DEBUGGEE!, COMMAND is NOT executed and just logged as an execution trace.
What I realized today during troubleshooting is that the concurrent use of the debugtrace was in conflict with that, as the use of the ! there skips the execution of the following script:
When $DEBUG refers to DEBUGGEE![N], an exit command is printed so that the caller exits prematurely [with an exit status of N].
Yeah, I've come a long way since I started skipping the cleanup via a simple check if $DEBUG was defined, and manually inserting set -x at the top of scripts to enable tracing…
Okay, back to the problem: To avoid the problem of the overload of the ! extension, I can simply assign a module to the cleanup; i.e. I need to insert a --module cleanup argument behind debugcommand. To find all affected places, I need to find all uses of the original, as-yet unchanged command. This command-line did the trick:
$ Unixhome-ingo --no-header --command 'ackf "debugcommand .* rm" | fackF "debugcommand --module cleanup" | prependCwd' | fv
Unixhome-ingo is an iterator over all of my personal projects; without a passed command, it will simply list the file system locations of all working copies --no-header omits the per-project header lines that would interfere, and the … | fv pipeline reads filespecs generated by the iterator and (after optional interactive filtering) sends them to my editor.
fv Open selected files from standard input in GVIM.
ack: ack "debugcommand .* rm" finds all cleanup invocations, ack "debugcommand --module cleanup" only those that have already been fixed.
ackf List all files containing pattern.
fackF List all files not containing pattern in files from standard input.
ackf produces matching filespecs as output, fackF (the f stands for file, to the left means input and to the right means output, the capitalization F means inverted matching) searches files and produces non-matching files. Those aliases are just abbreviations of built-in ack options: alias ackf='ack --files-with-matches' alias fackF='ack --files-from=- --files-without-match'This produces the desired matches first search but does not match second search condition.
prependCWD concludes the search, and simply prepends the current working directory to all found filespecs. As the iteration visits each project, any search results are relative to the project directory, but we need to concatenate and consume everything together, so we need absolute filespecs. In GVIM, I use :argadd to put those files onto the argument list, then go through them one by one to do the edits. An ad-hoc search command quickly brings me to all locations, and the :wn[ext] command to the following file. I could have recorded a macro or written a :substitute command, but that wouldn't have saved much time as I would need to take a closer look at the diffs then. Another benefit of the manual edits is that one may accidentally see other issues or outdated styling that can be corrected on the go (boyscout rule).
Ingo Karkat, 26-Oct-2026
ingo's blog is licensed under Attribution-ShareAlike 4.0 International