I should write more shell scripts than I do
For reasons beyond the scope of this entry, I've been manually maintaining a group of files in sync across three 'machines' (my GNU Emacs configuration). There is no master machine for edits (it depends on what I'm on at the time when I need a change), and I don't always propagate changes immediately after I make them. Plus, some of these files are in various places under my home directory. I made a lot of use of 'ssh <host> cat <relative path>/file | diff -u - file' and the like to keep track of this all and make sure I wasn't overwriting changes when I propagated things, and I kept doing it by hand for various reasons.
(I'm aware that some people would solve this problem with things like a Git repository. This isn't my way for various reasons.)
Today I sat down and wrote remdiff and remsync scripts to do this
for me. These scripts are a little bit more elaborate than they need
to be (partly because I put in a reasonable amount of error checking),
but they still weren't much work and they're not very long. Getting a
reasonably decent design for the command line arguments took a few
iterations of writing code and then using the scripts and being
unhappy, and I'm probably going to fiddle more, but the result is
usable already.
The existence of these scripts doesn't just reduce the amount of effort and irritation involved in this process. Because they make it easier to do this, they encourage me to do it more often and thus reduce the chances I will have pending changes on more than one machine that I'll have to reconcile. They also make it feasible to bulk check a whole collection of files, which found some differences I hadn't realized.
Writing shell scripts is one of those Unix superpowers. Unix has such a collection of reusable tools and tool components (plus various shell features that are designed to make scripting easier) that it's practically a shame not to use them. But in this case and others, I'll tell myself over and over that it's a bit too trivial or too much work or whatever to turn something into a shell script. So today I was once again reminded that I should write more shell scripts than I do.
Another non-obvious advantage of shell scripts is that you can adjust their command line arguments to exploit shell features like filename completion. In the original manual version, I couldn't use shell filename completion for the '<relative-path>/file' bit because I was already in the <relative-path> directory on my machine. Since I no longer have to supply this argument by hand, I can do things like 'remdiff host: fly<TAB>' to have my shell auto-complete the filename to 'fly-startup.el'.
(People who use fancy shells could arrange for their shell to know how to autocomplete hostnames as well, bringing this down to, say, 'remd<TAB> ho<TAB> fly<TAB>'. For this sort of personal command you could adopt a very brute force approach based on knowing what hostnames you're likely to ever use it on. If you want to use another host, you add it manually to your completion information for this command.)
PS: It figures that the moment I had 'remdiff' I started using it to check other files that I generally keep in sync between multiple systems. Apparently this was yet another one of those little points of friction that I hadn't realized I had.
(One reason I held back on writing 'remdiff', which was the first of the two scripts, was that I expected parts of it to be an annoying pain to write, like going from the absolute path of the current directory to its relative path from $HOME. And I was right, bits of the script do feel like an annoying pain, but it's worth it.)
Sidebar: My brute force approach to getting the relative path
In Bash:
HDIR=$(realpath "$HOME")
MPWD=$(pwd)
# We could be in $HOME.
if [ "$HDIR" = "$MPWD" ]; then
RELDIR="."
else
RELDIR=${MPWD##"$HDIR"/}
fi
(Using '##' this way is actually a standard Bourne shell feature now that I look.)
As far as I know there's no command that will do the '${..##../}' bit
to strip off some but not all leading directory components on a path.
I need the realpath here due to a long standing local feature that
causes my $HOME to have a symlink in it.
(I've decided it's a feature that I'm not explicitly restricting this to be under $HOME; if I'm outside of $HOME, it will use the same absolute path on the local and remote machines. This turns out to be convenient for some things.)