Do Shell Script Grep Action Fails When Script Returns Nothing

I have two files that are similar. I'm trying to get the lines that are in file 2 but not file 1, using the shell command grep -Fxv -f file-1.txt file-2.txt

This works fine in Terminal, and it works fine in Keyboard Maestro except in one case.

If there are lines that are in file 2 but not file 1, the script returns those lines both in Terminal and in Keyboard Maestro.

If there are no lines that are in file 2 but not file 1, the script returns nothing in Terminal, as expected. But in Keyboard Maestro, the script action fails with the error message Task failed with error message 1.

Any idea why this would happen?

Like many "Unix-y" tools, grep uses “exit codes” to report back success or failure.

Quoting the relevant portion of man grep (which is how you get the ‘manual’ page for ‘grep’ in Terminal.app):

EXIT STATUS
     The grep utility exits with one of the following values:

     0     One or more lines were selected.
     1     No lines were selected.
     >1    An error occurred.

To put it another way:

  • if grep finds at least one ‘match’ for whatever you searched for, it will exit as zero
  • if grep successfully searches for what you looked for, but does not find any matches, it will exit one
  • if grep cannot do what you asked it to do (i.e. you asked it to search a file, but that file does not exist) then it will exit as two or greater

You can see this in Terminal.app by adding echo "$?" after your command:

grep -Fxc -f ~/Dropbox/LLP/21-01-29.txt ~/Dropbox/LLP/21-01-28.txt; echo "$?"

$? will show you the exit-code for the previous command.

Keyboard Maestro sees a non-zero exit code for any shell script as an indication that the shell script failed, and therefore should not continue with the rest of the macro.

The most basic way to work around this is add exit 0 to the end of your shell command:

grep -Fxc -f ~/Dropbox/LLP/21-01-29.txt ~/Dropbox/LLP/21-01-28.txt
exit 0

Since you are using “Display Results in a Window” you will know that grep did not find anything if you see nothing in the window.

However, that is a bit of a “blunt instrument” approach. It could be misleading because it will not tell you about errors.

It would be more helpful to have the shell script interpret the exit code and report back accordingly:

#!/usr/bin/env zsh -f

	# Keyboard Maestro will not see anything installed in /usr/local/bin
	# unless you tell it to look there.
PATH='/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin'

grep -Fxc -f ~/Dropbox/LLP/21-01-29.txt ~/Dropbox/LLP/21-01-28.txt

EXIT="$?"

if [[ "$EXIT" == "0" ]]
then
	# we don't actually need to do anything in this case
	# as `grep` will have already shown you the results
	# so we just use a `:` to tell the shell to continue
	:

elif [[ "$EXIT" == "1" ]]
then
		# if we get here, `grep` searched successfully,
		# but did not find anything. So let's report that:

	echo "grep did not find any matches"

else
		# if we get here, `grep` encountered an error
	echo "grep encountered an error and reported an exit code of: $EXIT."

		# this next line will cause this script to exit with the
		# same code that `grep` exited with.
		# Keyboard Maestro will interpret this as a failure
		# if you do not want that, remove the next line
		# or put a # in front of it
	exit $EXIT
fi

	# this will tell the shell to exit with code = zero if it reached this point
exit 0

I hope this helps. Please let me know if any of it is unclear. (Note that the code in the box is not fully shown by the forum software, so you may have to scroll to see it all.)

2 Likes

This is great—you've solved my problem and I've learned something. Thank you!

Hey TJ,

Well said.

I have a couple of things to add.


Keyboard Maestro has quite a few options for handling errors in the Execute a Shell Script action:

image

If you're displaying the result of a shell script in a window you can simply turn on “Include Errors”.

You can stop KM from terminating the macro if there is an error in the shell script action.

You can also do something like this:

image

The first action allows the error to pass through by turning off the relevant items in the second highlight of the action/gear menu.

The second action is displaying the text token %ActionResult%. This action normally returns "OK", but for a failed shell script like this one it might be something like:

Task failed with status 1

So should you wish you can put your error-handling outside the shell script action itself and use an If Then Else action with a text-condition.

That said – I like doing error-handling within the script itself, because you can make the output more predictable.

Absolutely true, but I'm not seeing the specific relevance here.  Grep on a Mac should be here and should not require the user to alter their path.

echo 'Unadulterated Keyboard Maestro $PATH:'"\n"
echo "$PATH\n"
echo "·····································\n"
type grep

OUTPUT:

Unadulterated Keyboard Maestro $PATH:

/usr/bin:/bin:/usr/sbin:/sbin

·····································

grep is /usr/bin/grep

When I need to change the path of an individual action I usually add these paths to the default:

export PATH=/opt/local/bin:/opt/local/sbin:/usr/local/bin:$PATH;

BUT – I find it much more convenient to add an environment variable to Keyboard Maestro's variables panel in the KM prefs.

Mine has grown over the years and won't be for everyone:

Variable name: ENV_PATH

Contents:

/opt/local/bin:/opt/local/sbin:/Users/chris/perl5/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/TeX/texbin

This environment variable will hold sway in all Execute a Shell Script actions, so you don't have to worry about about fixing the path on a per action basis anymore.

I've pulled my hair out a few times trying to troubleshoot a failed shell script, until I realized I hadn't emplaced a full path string.

So I finally got fed up – added the environment variable – and the sailing has been smoother ever since.

-Chris

2 Likes