Bash script evaluates differently via term and MacOS launchd

Viewed 46

I have a very simple bash script that curls a URL, counts a substring in the response, and runs some code if it finds the string.

Originally, I wrote it like this:

RESP=$(curl -s 'https://theurl.com' | grep -ic substring)

if [ $RESP > 0 ];
  do something
else
  do something else
fi

This worked great from my terminal prompt.

When I set it up to run in launchd as a local user launch agent, the if statement never evaluated to true.

Things I tried during debugging:

  1. Ensuring both were using /bin/bash (they were) and running as me.
  2. Sending the variable to stdout to confirm.
  3. Running them without the curl, just grepping a local file.

Eventually, I was able to get it to work by changing the if to:

if [ $RESP -ge 1 ]

That works in both places, but I can't figure out why the exact same script in the exact same interpreter would evaluate differently in both places.

Any ideas?

2 Answers

It's because the > doesn't do what you think it does. The command

[ $RESP > 0 ]

is treated by the shell as a funny way of writing

[ $RESP ] > 0

That is, it runs the command [ (yes, that's an actual command, essentially equivalent to test) with the expansion of $RESP and "]" as arguments. The fact that > 0 is in the middle of the arguments to [ doesn't matter; you can put a redirection operator anywhere in the command ... argument(s) sequence, and it does the same thing.

Net result: [ checks to see if $RESP expands to a non-blank string (succeeding if it does), and sends its (empty) output to a file named "0".

At least, that's what it does normally. I suspect when you run it as launch agent, it's getting an error creating the "0" file, so it fails. And since it fails, and it's the condition of an if statement, the else clause runs.

To use > or < in a [ ] test expression, you need to quote or escape it, like [ "$RESP" ">" 0 ] or [ "$RESP" \> 0 ]. Also, > is the wrong test operator anyway; if you're comparing integers, use -gt instead. In a [ ] test expression, > denotes sorting order comparsion (so [ 9 ">" 10 ] is true, because "9" sorts after "1").

This seems very strange, but as Cyrus pointed out in the comments, the command [ $RESP > 0 ] creates a file named 0. If you run the script from launchd the creation of such a file probably fails because the working directory is different from when you run the script in your terminal. Because the file could not be created, the if ... check fails and the else branch is entered.

Now, why on earth does this create a file. bash -c 'help test' ([ is a synonym for test) clearly states

STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.

However, before test/[ sees the > bash processes it and interprets it as a redirection. The following three commands are actually equivalent:

echo string > file
echo > file string 
> file echo string

Therefore, you do the test [ $RESP ] and redirect the (empty) output to the file 0.

To use the > from test you have to quote it, like so: [ $RESP ">" 0 ]. However, as you already noticed, you wanted to use -gt instead.

Note: To check if any matches were found you could switch to grep -q instead:

if curl -s 'https://theurl.com' | grep -iq substring; then
  # do something
else
  # do something else
fi
Related