Touch and run

Here’s one last bit of followup on my Finder/Terminal tool posts. In the first post on the topic, I mentioned that I had created a bunch of zero-length JPEG files using the touch command. And in both the first and second posts, I talked about how long the sel command took when there were hundreds of files to select. I thought it worth a post on how I made hundreds of zero-length files to test out the script.

The short answer is this:

run -f 'img-{:03d}.jpg' 500 | xargs touch

which created, in the current directory, 500 files named img-001.jpg through img-500.jpg. We’ll turn this into a long answer by going through each part.

You won’t find the run command on your computer unless you read this post from a few years ago, in which I gave its Python source code. But I used run because I have it and like it. We’ll get to using commands that are on your computer further down.

The usage message for run is this:

Usage:
run [options] <stop>
run [options] <start> <stop>
run [options] <start> <stop> <step>

Generate a run of integers or characters. Similar to jot and seq.

Options:
    -f FFF   formatting string for number
    -s SSS   separator string
    -c       characters instead of integers
    -r       reverse the run
    -h       show this help message

The run of numbers can be integers or reals, depending on the values of start, 
stop, and step. The defaults for both start and step are 1. If -c is used, 
then start and stop must both be given as characters and step (if given) is an 
integer.

As you can see, I used run with an -f option to put each number generated into a string. The formatting code used in the argument to -f follows the Python format string syntax. Most of the code is repeated verbatim; the part inside the curly braces tells run to format each number as three characters long with leading zeros, if necessary. The output of the run command is

img-001.jpg
img-002.jpg
img-003.jpg
.
.
.
img-499.jpg
img-500.jpg

What we want to do is run the touch command with each one of these filenames as its argument, e.g.,

touch img-001.jpg

The main purpose of touch is to update the modification timestamp on the given file. But if the file given as the argument to touch doesn’t exist, a zero-length file of that name is created. It’s this feature we’re going to exploit.

The problem with the way touch works is that it doesn’t take the filename from standard input—it needs it to be an argument. So we can’t just pipe the output of run into touch. Luckily, the xargs command is available. It constructs an argument list from standard input (the list of img-nnn.jpg file names) and executes the given utility (touch) with that argument list. Boom. On my M1 MacBook Air, it takes about a tenth of a second to create all the files.

But run isn’t necessary. There are two commands already on your Mac, jot and seq, that do much the same thing as run. I wrote run because I don’t like the syntax of either jot or seq, but they’re both fine for this simple case. We can create the same files as above with

jot -w 'img-%03d.jpg' 500 | xargs touch

or

seq -f 'img-%03.0f.jpg' 500 | xargs touch

Both jot and seq use printf-style formatting codes, which are the parts that start with percentage signs. Note that jot uses -w instead of -f as the option and that seq treats the numbers as floating point values instead of integers. This latter is why its number formatting bit has to be %03.0f instead of the simpler 03d. These minor annoyances are some of the reasons I wrote run.

Unsurprisingly, jot and seq are faster than run, but they’re all so fast I had to use the time command to learn the difference between them. My preference for the syntax of run far outweighs its tiny additional runtime.

Update 24 Sep 2024 4:37 PM
Are you surprised to see an update? You shouldn’t be; there’s always more than one way to do it. In this case, the additional way was suggested by Jonathan Buys on Mastodon and it uses brace expansion:

touch img-{001..500}.jpg

No need for piping. Although I’ve linked above to the bash manual, the brace expansion works in zsh, too.

For me, there are a few downsides to this:

  1. I find it hard to remember brace expansion (although I may find it easier after writing this).
  2. When I do remember brace expansion, I tend to use a hyphen between the numbers instead of a pair of periods, and that doesn’t work at all.
  3. I like to double-check the filenames before creating the files, and I can issue the run command by itself to make sure the names are what I want before adding the pipe to args touch.

Now, it’s true that the Jonathan’s brace expansion solution can be checked by running something like

echo img-{001..020}.jpg

to check the file names (in this case, there’s no need to generate all 500) before reissuing the command with touch instead of echo, so there’s more than one way to run a test, too. Thanks to Jonathan for the suggestion!