3

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?

MichaelM
  • 45
  • 4
  • After `[ $RESP > 0 ];` you are missing a `then`. However, without the `then` I'd expect the script to always fail. Did you just miss it in your question? – Socowi Mar 02 '21 at 00:02
  • 1
    Does the script begin with `#!/bin/bash`? – Barmar Mar 02 '21 at 00:06
  • 1
    Always quote your variables unless you have a good reason not to. – Barmar Mar 02 '21 at 00:07
  • 3
    With `sh` or `bash` `[ $RESP > 0 ]` creates a file named `0`. – Cyrus Mar 02 '21 at 00:11
  • General rule in bash comparisons inside `[` or `[[`: use the "lettery" comparison `-gt` for numbers, and the "numbery" comparison `>` for strings, the opposite of what you'd think... – xdhmoore Mar 02 '21 at 00:43

2 Answers2

2

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").

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • Thank you. I suspected it had something to do with the evaluation syntax but I could not figure out why worked in my own shell. This explains it. If launchd would have thrown an error I would have zeroed in on it quickly. Thanks for the bash lesson! – MichaelM Mar 02 '21 at 13:00
  • 1
    @MichaelM Unfortunately, launchd jobs run without any connection to the user interface, so they don't have a good way to report errors. For debugging, you can direct standard & error output to files by adding the `StandardOutPath` and `StandardErrorPath` keys to the .plist. But note that you need to send them somewhere the job has permissions to write to. – Gordon Davisson Mar 02 '21 at 19:13
2

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
Socowi
  • 25,550
  • 3
  • 32
  • 54