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!


Improved Finder/Terminal tools

A couple of days ago, I got an email from Loren Halter, who had some improvements to my Finder/Terminal tools. I was going to add another update to that post, but realized I had more to say about Loren’s stuff than would fit comfortably in an update. So here we are with a new post.

First, Loren put his work in this gist, so you can review and copy them for your own use. There are three functions Loren includes in their .zshrc dotfile1 to ease the use of the Finder and Terminal together. They are lsf, cdf, and sel, and we’ll go through each of them in turn.

lsf is a variant of the ls command that lists, in the Terminal, the contents of the front Finder window. I can’t say that I foresee myself using this function, as the Finder window itself shows its contents. Presumably, Loren uses this to feed a list of file names to another command, but I prefer doing that sort of thing with my Terminal’s working directory set to the directory that contains the files of interest; in such cases, ls suffices. But it may be just the thing for you.

Next comes cdf, which changes your working directory to that of the front Finder window. It is essentially the cd command combined with my ;dir abbreviation. While it’s not a huge timesaver, I do this combination enough that I decided to add it to my .bashrc. As I was scrolling through the file to find a good place to put it, I found a function called cdff, which was apparently a long-forgotten attempt on my part to do the same thing (I assume the ff part meant “front Finder”). I won’t show it to you, because both the AppleScript and shell scripting aspects of it were a horrible mess. I suspect it didn’t even work, which is why I don’t remember anything about it.

Anyway, I decided to take Loren’s idea for cdf and make my own version of it:

bash:
1:  function cdf() {
2:    target=$(osascript -e 'tell application "Finder" to return POSIX path of (target of front Finder window as alias)')
3:    cd "$target"
4:  }

Comparing Loren’s version with mine, you’ll see the error handling code in theirs that I’ve not included in mine—I’m just an outlaw, I guess. What I wanted was for my cdf to work exactly as combining cd with ;dir would, and I don’t really see much danger in not including the try/on error code.

(What Loren’s code handles that mine doesn’t is the case in which there are no open Finder windows. Loren’s cdf will then cd into the Desktop folder; mine will belch out an error message. I don’t want that protection because my goal is to never work in the Desktop directory. In my experience, doing so, even “temporarily,” leads to a clutter of files on the Desktop that sit there far longer than they should. If you’re more disciplined than I am, by all means, use Loren’s version.)

Which leaves us with sel. I mentioned in my earlier post that my version of sel could take several seconds to run if it’s selecting hundreds of files. In my tests, Loren’s code runs in about a third of the time as mine. That considerable reduction in runtime came from changing the way AppleScript constructs the list of files to select. You may recall that my version of sel generates AppleScript that looks like this

applescript:
tell application "Finder"
    set theFolder to target of front window as alias
    set theFiles to {}
    tell folder theFolder
            set end of theFiles to file "20240915-001.jpg"
            set end of theFiles to file "20240915-002.jpg"
            set end of theFiles to file "20240915-003.jpg"
            set end of theFiles to file "20240915-004.jpg"
            set end of theFiles to file "20240915-005.jpg"
            set end of theFiles to file "20240915-006.jpg"
            set end of theFiles to file "20240915-007.jpg"
            set end of theFiles to file "20240915-008.jpg"
            set end of theFiles to file "20240915-009.jpg"
            set end of theFiles to file "20240915-010.jpg"
        end tell
    select theFiles
    return
end tell

and then runs it. Loren’s sel generates AppleScript that looks more like this:

applescript:
tell application "Finder"
    set theFiles to {}
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-001.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-002.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-003.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-004.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-005.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-006.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-007.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-008.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-009.jpg" as alias
    set end of theFiles to POSIX file "/Users/drdrang/Library/Mobile Documents/com~apple~CloudDocs/blog-stuff/finder-terminal/20240915-010.jpg" as alias
    select theFiles
    return
end tell

Now, I think my code is more elegant looking, and it takes advantage of some nice AppleScript features; but again, Loren’s runs in about a third of the time. Loren believes this speedup comes from his code using the full path to each file, eliminating the work AppleScript has to do in the tell folder block of my code. I agree.

When faced with the choice between elegant but slow autogenerated code that no one sees and cruder but speedy autogenerated code that no one sees, I know which one I’m going to take. I still preferred having sel as a command script rather than a function, and I also wanted it written in my style to make it easier to update if necessary. So I incorporated Loren’s insight into my sel, giving me this:

bash:
 1:  #!/bin/zsh
 2:  
 3:  # Open Finder window to the current directory and select all the files
 4:  # in that directory whose names are passed to this script via stdin.
 5:  
 6:  # Open a Finder window to the current directory. 
 7:  open .
 8:  
 9:  # Construct the AppleScript in three parts.
10:  # 1. Initialize variables and start telling theFolder.
11:  applescript='tell application "Finder"
12:    set theFiles to {}
13:  '
14:  
15:  # 2. Add all the files from stdin to theFiles list
16:  while read f; do
17:    thisFile="$PWD/$f"
18:    applescript+="  set end of theFiles to POSIX file \"$thisFile\" as alias
19:  "
20:  done
21:  
22:  # 3. Stop telling theFolder and select theFiles. Return nothing.
23:  applescript+='  select theFiles
24:    return
25:  end tell'
26:  
27:  # Run the AppleScript.
28:  echo "$applescript" | osascript -

The key is in Lines 17–18, where the full path to each file is assembled using the PWD environment variable.

Again, Loren’s code has error handling that mine doesn’t, although in this case the error handling basically just throws up a dialog box telling you what the failure is. In my sel, error messages are printed in the Terminal by the system.

Thanks to Loren for the code improvements and a better understanding of AppleScript.


  1. I think they’ll all work just as well from a .bashrc file if you’re a stick-in-the-mud like me. 


Am I blue?

The popular “Is my blue your blue” game is questionable as a test of color perception, in that monitor settings and lighting conditions differ, but it presents its results in a way that I really like.

The game fills your screen with a series of colors along a blue-green spectrum and asks you whether the displayed color is blue or green. The colors get closer together as the game proceeds until it has enough answers to tell you the boundary between your ideas blue and green. If you’re like me, the last few colors should all have been answered “neither,” but that’s not a choice. The idea is to insist on a binary choice and find your boundary between the two.

I played it yesterday on my iPhone and got these results:

Blue-green cumulative distribution function

What I like about the graph is that it’s both clever and self-explanatory. When you look at it, these are the things you know immediately:

  1. The horizontal axes represents the hue, which has been spread across the entire background of the graph. This is a much better way of presenting the hue than using a value.
  2. Your threshold between green and blue is the dashed vertical line. This comes from both the graph and the text below it.
  3. The vertical axis represents the fraction (or percentage) of people who categorized all colors to the left of the given color as blue. We know that because the jagged S-shaped curve, called the “threshold distribution” in the little legend at the bottom right corner, starts out low at the green end and runs up to the top as it moves toward the blue end. Also, the text says my boundary was at the 57% mark, and the vertical line intersects the jagged curve somewhat above the center of the graph.

Those of us who remember our probability and statistics class would call the jagged curve the cumulative distribution function (CDF) of the population’s blue/green threshold. But you don’t need to know this to figure out what the jagged curve means. I assume that the “population” in this case is the people who’ve played the game and that the CDF curve gets updated with every play.

So what we have is a game that presents statistical information at a glance without any long-winded explanation.1 The only improvements I would make are:


  1. Like the explanation I just gave. 


Finder/Terminal tools

When I’m working at my Mac, some things are most effectively done using the Finder’s GUI and some are best done through the command line in the Terminal.1 I switch between the two with the help of a handful of simple automations. I know I’ve written about one of them before, but I don’t think I’ve written about the others. Certainly not as a collection. So here are four little tools I use to go back and forth between the Finder and the Terminal.

open .

The open command is one of Apple’s (originally NeXT’s) great contributions to the integration of the command line and the GUI (the others are pbcopy and pbpaste). It can be used to open documents, but I use it most often this way:

open .

This command, when invoked in the Terminal, activates the Finder and opens a window to the current working directory (that’s the dot). It’s smart enough to know if there’s already a Finder window showing that directory; if so, it just brings that window to the front instead of creating a new one.

Terminal Here

The converse of open . is a Keyboard Maestro macro I wrote called Terminal Here. When the Finder is active, invoking Terminal Here via the ⌃⌥⌘T keyboard shortcut opens a new Terminal window and sets its working directory to the one shown in the frontmost Finder window. Because it was written by me, not Apple, it isn’t as smart as open . in that it will open a new Terminal window even there’s already one open and set to that directory. In the many years I’ve used this macro, I haven’t found that lack of intelligence a problem, so I haven’t put any effort into making it smarter.

You can download the macro or just build it yourself. Here’s what it looks like in the Keyboard Maestro editor:

KM Terminal Here

It’s just a single step that runs this AppleScript:

applescript:
1:  tell application "Finder"
2:    tell front Finder window
3:      set myPath to POSIX path of (target as alias)
4:    end tell
5:  end tell
6:  
7:  tell application "Terminal"
8:    activate
9:    do script "cd " & quote & myPath & quote
10:  end tell

The first stanza, Lines 1–5, gets the path to the directory shown in the front Finder window. The second stanza, Lines 7–10, activates Terminal and cd’s into that directory. Pretty simple, and you can easily change the second stanza to work with iTerm if that’s your terminal emulator of choice.

Update 16 Sep 2024 7:27 PM
Leon Cowle pointed out on Mastodon that if you have the path bar visible in your Finder windows (as I do—you can see it in a screenshot further down in the post), you can right-click (or control-click or two-finger click) on the icon or folder name down there and a context menu will appear with Open in Terminal as one of the items. Choosing it will do what Terminal Here does. This is part of macOS—going back to OS X, I think—so you don’t need a utility like Keyboard Maestro to open a Terminal window this way. Also, you can do the same thing with any of the folders shown in the path bar.

I won’t be using this myself, as I find it easier to type ⌃⌥⌘T than to invoke a context menu, but it’s always nice to know things like this. Thanks, Leon!

;dir

Let’s say I’m working in the Terminal and I want to change to a new directory. That’s what the cd command is for, of course, but typing the path to the new directory can be tedious—even when taking advantage of tab completion. If you have a Finder window open to the directory you want to change to, Apple has a cute way around the tedium: drag the proxy icon from the Finder window’s title bar into the Terminal after typing cd and the path will be inserted at the caret.

What’s the proxy icon? It’s the little picture of a folder that appears next to the window’s title.

Finder title bar with proxy icon

This icon used to be a permanent part of the Mac’s title bars but was stupidly changed in Big Sur to be a peekaboo feature—it would normally be hidden but would appear if you put the mouse pointer over the title and waited a bit. The following year, Apple showed that it sometimes does listen to reason by adding an Accessibility preference that lets you make proxy icons permanently visible.

Anyway, while I like the idea of dragging proxy icons, it isn’t especially efficient. So I created a TextExpander snippet, triggered by typing ;dir, that would insert the path of the frontmost Finder window. When I switched from TextExpander to Typinator, I migrated it over. Both TextExpander and Typinator (and other utilities like them) have an option for running an AppleScript when an abbreviation is typed. Here’s the AppleScript that’s run when I type ;dir:

applescript:
tell application "Finder" to get quoted form of POSIX path of (target of front Finder window as alias)

It’s kind of long because of AppleScript’s “this of that of the other” syntax.

Update 16 Sep 2024 7:27 PM
As a follow-on to the update in the Terminal Here section, the context menu that pops up when you right-click on an element in the path bar has an item named Copy folder name as Pathname, where folder name is the name of the current folder. This will put the full path onto the clipboard, so you can paste it anywhere. I prefer using ;dir because it’s faster and keeps me in the Terminal, but it’s nice to know other options.

sel

The previous three automations are really simple, and I use them quite often. This last one was the most difficult to build, and I rarely use it. But when I do use it, it’s very handy. It’s a shell script named sel that’s meant to be run at the end of a pipeline. It expects a list of files to be passed into it via standard input and then opens a Finder window in the current working directory and selects those files. For example,

ls *.png | sel

will activate the Finder, open a Finder window in the Terminal’s working directory, and select all the files that end with .png.

The idea behind sel is that I want to use the Finder to do something with a bunch of files, but it’s easier to select the files with a shell command than it is by using the GUI. Suppose, for example, I have a directory filled with photos. The name of each JPEG file starts with the date on which the photo was taken in yyyymmdd format. If I want to select all photos taken on a certain date (perhaps because I’m going to attach them to an email or copy them to another folder) I could drag or shift-click my way through them, but it’s faster and more accurate to run

ls 20240915*.jpg | sel

and immediately see the results:

Finder window with several files selected

(If you’re wondering why all these JPEG files are zero bytes long, it’s because I made them using the touch command specifically for this example.)

I probably wouldn’t use sel if I had only ten files to select, but definitely would if I had dozens or hundreds.

By the way, I’m not thrilled with the sel name. I’d rather it were select, but there’s already a built-in bash command named select. For a while, I called my script fselect but found that I was forgetting the initial f. Time will tell if this shorter name sticks with me.

Oh, you want to see the code for sel? Here:

bash:
 1:  #!/bin/zsh
 2:  
 3:  # Open Finder window to the current directory and select all the files
 4:  # in that directory whose names are passed to this script via stdin.
 5:  
 6:  # Open a Finder window to the current directory. 
 7:  open .
 8:  
 9:  # Construct the AppleScript in three parts.
10:  # 1. Initialize variables and start telling theFolder.
11:  applescript='tell application "Finder"
12:    set theFolder to target of front window as alias
13:    set theFiles to {}
14:    tell folder theFolder
15:    '
16:  
17:  # 2. Add all the files from stdin to theFiles list
18:  while read f; do
19:    applescript+="    set end of theFiles to file \"$f\"
20:    "
21:  done
22:  
23:  # 3. Stop telling theFolder and select theFiles. Return nothing.
24:  applescript+='  end tell
25:    select theFiles
26:    return
27:  end tell'
28:  
29:  # Run the AppleScript.
30:  echo "$applescript" | osascript -

After opening a Finder window in Line 7, it constructs an AppleScript that does the selecting of files. The exact form of the AppleScript depends on the filenames passed into sel. For the example above, the AppleScript will be

applescript:
 1:  tell application "Finder"
 2:    set theFolder to target of front window as alias
 3:    set theFiles to {}
 4:    tell folder theFolder
 5:        set end of theFiles to file "20240915-001.jpg"
 6:        set end of theFiles to file "20240915-002.jpg"
 7:        set end of theFiles to file "20240915-003.jpg"
 8:        set end of theFiles to file "20240915-004.jpg"
 9:        set end of theFiles to file "20240915-005.jpg"
10:        set end of theFiles to file "20240915-006.jpg"
11:        set end of theFiles to file "20240915-007.jpg"
12:        set end of theFiles to file "20240915-008.jpg"
13:        set end of theFiles to file "20240915-009.jpg"
14:        set end of theFiles to file "20240915-010.jpg"
15:      end tell
16:    select theFiles
17:    return
18:  end tell

The loop in Lines 18–21 of the shell script creates all the lines in the tell folder section of the AppleScript.

After the AppleScript is built and saved in the applescript variable, Line 30 then runs it by passing it to the osascript command.

I should mention a few things:

  1. sel has to be run from the directory with the files you want to select. Being able to go up or down a folder hierarchy is beyond my coding skills. This limitation has never got in my way.
  2. sel can take a few seconds to run if it’s selecting hundreds of files. The Finder window will appear immediately, but it will take a little while before the files are selected. AppleScript isn’t especially fast.
  3. I’ve put zsh in the shebang line of sel because zsh is the Mac’s default shell, but the script runs the same under bash. If you want to translate the script to tcsh, you’re on your own.

Update 21 Sep 2024 10:12 AM
There’s new post with further examples and improvements.


  1. Or iTerm or whatever terminal emulator you choose. I used iTerm for several years, but moved back to the Terminal some months ago.