January 23, 2022 at 12:58 PM by Dr. Drang
I’m trying to run a very simple shell script:
cd /tmp echo "$KMVAR_var1" > file1.txt echo "$KMVAR_var2" > file2.txt diff file1.txt file2.txt > diff_result.txt
I don’t actually want the diff results in a file, I want them in a variable returned by the shell script action. But because that was failing, I tried the above to write it to a file instead. But it still failed (with the generic failed in shell script message).
Rob learned that the macro failed only when
diff was the last command in the shell script. Adding a final innocuous command, like
echo "foo" to the end of it got rid of the error. And of course, the script—without the
echo "foo" line—worked just fine when run from the command line.
How can this be? My first thought was that the error had something to do with interactive vs. noninteractive shells, but that led nowhere. So I made my own version of Rob’s macro and changed the last line from
The shell script in the last action is
cd ~/Desktop/griffdiff echo "$KMVAR_InstanceVar1" > file1.txt echo "$KMVAR_InstanceVar2" > file2.txt comm file1.txt file2.txt
This worked fine. I think of
diff as being similar, so success with
comm and failure with
diff was a real stumper. There were no clues in
diff’s man page. I put the problem aside to think about in the evening.
As luck would have it, when I returned to the problem I was using my iPad, so when I decided to review the man page again, I used this online version instead of the one included with macOS. And there, down at the end of the DESCRIPTION section, was the answer:
FILES are 'FILE1 FILE2' or 'DIR1 DIR2' or 'DIR FILE' or 'FILE DIR'. If --from-file or --to-file is given, there are no restrictions on FILE(s). If a FILE is '-', read standard input. Exit status is 0 if inputs are the same, 1 if different, 2 if trouble.
Emphasis mine. I went back to look at the error message Keyboard Maestro gave when the shell script action ended with
diff. Without the timestamps it was
Execute macro “Griffdiff” from trigger Editor Action 222451 failed: Task failed with status 1 Task failed with status 1. Macro “Griffdiff” cancelled (while executing Execute Shell Script).
The “Task failed with status 1” message was not—as I had previously thought—giving me a status code generated by Keyboard Maestro itself. Instead, KM was just passing along the status code it had received from the shell.
diff had returned an exit code of 1 because the inputs were different. Keyboard Maestro then interpreted the nonzero exit code as an error and bailed out. So everything was working just as it was supposed to.
But that didn’t fix Rob’s problem. Luckily, I remembered that I’d run into a situation some time ago in which I had to turn off Keyboard Maestro’s normal error handling. I did it by changing the “Failure Aborts Macro” and “Notify on Failure” settings in the action’s gear menu from ✔︎ to ✖︎.
With those two changes, the macro ran fine. Now I had two questions:
- How had I missed the exit status stuff when I looked at the
diff’s man page earlier?
- Why does
diffreturn a nonzero exit code when it does exactly what you want?
The answer to the first question was easy: Here’s what macOS Catalina’s
man diff says at the end of the DESCRIPTION section:
FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'. If --from-file or --to-file is given, there are no restrictions on FILES. If a FILE is `-', read standard input.
Nothing about exit status in that paragraph or anywhere else. According to the copyright notice, Catalina’s
diff man page was written in 2002 (way to keep on top of things, Apple!). The online version was updated in 2019.
I’m not sure about the answer to the second question, but my guess is that it works that way so
diff can be used in
if statements or those short circuit statements with
|| you often see in shell scripts, like
diff file1.txt file2.txt && echo "Identical files"
where the part after the
&& is executed only if the part before it returns a zero (success) exit code. Still, I found it surprising.
diff is probably used most often on files that are known to be different—it’s weird that using it that way produces an exit code that typically indicates failure.
By the way, if you’re wondering about the exit status of a command, you can learn what it is by running
immediately after the command. This works in both bash and zsh.