Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't this Bash script error out?

Tags:

bash

Here's my Bash script:

#!/bin/bash -e

if [ == "" ]; then
  echo "BAD"
  exit 1
fi

echo "OK"

And here's the output:

./test.sh: line 3: [: ==: unary operator expected
OK

The return code is 0.

There's an obvious syntax error at line 3. Rather than raise the syntax error and refuse to run the script, somehow the script just runs and reports the syntax error at run time. The -e flag hasn't protected me from this - apparently a syntax error in an if statement constitutes a false condition rather than a reason to immediately exit the program. BUT, somehow Bash has parsed that whole if ... fi block, so after ignoring the bad line, execution somehow resumes not at the next syntactically correct line but after the end of the block?

I have two questions:

  1. What is going on?
  2. How can I protect myself from this behaviour in future?
like image 872
qntm Avatar asked Dec 08 '15 13:12

qntm


2 Answers

  1. if runs the command [, and just examines its return code. Bash doesn't know nor care about the syntax for the [ command.

    You can put some other command there, and Bash still won't know anything about its particular syntax.

  2. Two things come to mind:

    • Using [[ instead of [: Bash does know and care about its syntax.

    • Using ShellCheck1; online, manually or within your favourite editor.

Both if and -e deal with exit codes: If it's non-zero if won't let you into the then block, and -e will exit. You can't really have both those behaviours at once. (Well, it seems [ exits with different codes for false results (1) and syntax errors (2), so it might be possible to ‘detect’ syntax errors.)


1Or some other tool, but that's the only one of which I know. Suggestions welcome.

like image 129
Biffen Avatar answered Nov 02 '22 23:11

Biffen


You don't have a shell syntax error here.

You have an error in the arguments to the [ command/builtin.

The reason set -e doesn't help here is because that's explicitly not what it is supposed to do. set -e would become entirely useless if you couldn't have if statements in your code with it on. Just think about that.

If you look in the POSIX spec for what the -e/errexit flag does you see this description:

-e

When this option is on, when any command fails (for any of the reasons listed in Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit with the following exceptions:

  1. The failure of any individual command in a multi-command pipeline shall not cause the shell to exit. Only the failure of the pipeline itself shall be considered.

  2. The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.

  3. If the exit status of a compound command other than a subshell command was the result of a failure while -e was being ignored, then -e shall not apply to this command.

This requirement applies to the shell environment and each subshell environment separately. For example, in:

set -e; (false; echo one) | cat; echo two

See point two there? That's your situation.

The reason the shell continues to execute is back to the "not a shell syntax error". You have a command error. The [ command/builtin attempted to parse its arguments and failed. It then returned an error return code. The if caught that, skipped its body and returned true (as per documented behavior of if when no conditions return true). So the shell script continued normally.

As I indicated in my comment, however, if you had used [[ (which is a bash-ism and a language construct) then your script would have had a syntax error and would have exited immediately on that line (at least in my tests).

like image 32
Etan Reisner Avatar answered Nov 03 '22 00:11

Etan Reisner