12

Consider this script:

#!/bin/sh

foo=1
if [[ ! -z $foo ]]; then
    echo abc
fi

It's using the Bash syntax [[ ... ]] which doesn't work (as expected) when I run it with the default shell on Ubuntu (dash). However, its return code is still zero.

$ ./tmp.sh
./tmp.sh: 4: ./tmp.sh: [[: not found
$ echo $?
0

How can I detect this kind of error in a script if I can't rely on the exit code?

2
  • 4
    That's not a syntax error. Commented May 23, 2021 at 4:23
  • Are you primarily thinking about scripts written by you (that you can modify), or about scripts that you may be not able/not willing/not allowed to modify? Commented May 23, 2021 at 10:06

2 Answers 2

14

Let me first explain why this happens. POSIX Shell Command Language spec says:

The exit status of the if command shall be the exit status of the then or else compound-list that was executed, or zero, if none was executed.

Since in your case then part is not executed and there is no else the exit status is 0. It would also be 0 if you ran this script using Bash as in man bash it says:

   if list; then list; [ elif list; then list; ] ... [ else list; ] fi

          The if list is executed.  If its exit status is zero,
          the then list is executed.  Otherwise, each elif list is
          executed in turn, and if its exit status is zero, the
          corresponding then list is executed and the command
          completes.  Otherwise, the else list is executed, if
          present.  The exit status is the exit sta‐ tus of the
          last command executed, or zero if no condition tested
          true.

How can I detect this kind of error in a script if I can't rely on the exit code?

There are 2 ways I could think of:

  • if you can modify your script add else part to the if construct:

      #!/bin/sh
    
      foo=1
      if [[ ! -z $foo ]]; then
          echo abc
      else
          echo not true
          exit 1
      fi
    
  • if you got if from someone and you're not willing to modify it use shellcheck static analyzer in sh mode to look for possible bugs in the code and report them to the author:

      $ shellcheck -s sh dash-exit-status.sh
    
      In dash-exit-status.sh line 4:
      if [[ ! -z $foo ]]; then
         ^-------------^ SC2039: In POSIX sh, [[ ]] is undefined.
            ^-- SC2236: Use -n instead of ! -z.
    
      For more information:
        https://www.shellcheck.net/wiki/SC2039 -- In POSIX sh, [[ ]] is undefined.
        https://www.shellcheck.net/wiki/SC2236 -- Use -n instead of ! -z.
    

Basically, this is a bug to me as one should not use non-POSIX features in scripts that are supposed to be executed by /bin/sh which might but doesn't have to be a symlink to Bash.

5

Note that as @muru commented, that's not a syntax error as far as Dash is concerned. [ is not a special character to the shell in the way ; and ( are. [ is just a regular command like echo, only with a funny name. In Bash/Ksh/Zsh [[ is a keyword like if, and keywords are part of the shell syntax, but crucially, in shells that don't support it, [[ would be treated as just another command.

Since Dash doesn't support [[, it's looked for in the PATH as any other command, and the lookup fails. As the answer by Arkadiusz Drabczyk says, the result here is an exit status of zero. If it was an actual syntax error, the shell would exit immediately with a non-zero status.

How can I detect this kind of error in a script if I can't rely on the exit code?

Well, technically, you could...

#!/bin/dash
[[ $1 == "foo" ]]
case $? in
    0)    echo "the first argument was 'foo'";;
    1)    echo "the first argument was something else";;
    127)  echo "[[ not supported";;
    *)    echo "there was some other error";;
esac

Or the same with ret=$? and a chain of if-elif with conditions against $ret. That's somewhat awkward to do, and not the best solution here, but if you really need to tell apart different error codes of some command, that's what you have to do.

If you're aiming at detecting the Dash vs. Bash case in particular, you could add an extra test at the start of the script to see if [[ works correctly:

#!/bin/bash
if ! [[ a == a ]] 2>/dev/null; then
    echo "[[ not supported, exiting." >&2
    exit 1
fi

# ...

Similarly, you could probably add tests for all other non-POSIX features you use, but testing them all can of course get a bit unwieldy.

To demand exactly Bash, and not just any shell that has the same features (Ksh and Zsh are compatible with some stuff, though not with all), check $BASH_VERSION at the start of the script:

#!/bin/bash
if [ -z "$BASH_VERSION" ]; then
    echo "Running only with Bash supported, exiting" >&2
    exit 1
fi

# ...

(The variable can of course be set manually, but than the user better know what they're doing.)

4
  • check $BASH_VERSION at the start of the script - if the script has to be run with Bash shouldn't changing the shebang be suggested too? One can easily fake that they're using Bash by setting BASH_VERSION environment variable. Commented May 22, 2021 at 21:12
  • @ArkadiuszDrabczyk, yep, the hashbang should indeed be set. Sure, the environment variable can be faked, but if someone does that, then they probably know what they're doing or at least deserve to pick up the pieces if something breaks... Commented May 22, 2021 at 21:20
  • Thanks for elaborating more on the distinction between syntax and commands Commented May 25, 2021 at 1:55
  • @vitalstatistix, on a second look, that paragraph wasn't too well written. For Bash, a broken [[ would be a syntax error, the same way as a broken if construct would be. if ! [[; then echo hmm; fi is a syntax error in Bash; but not in Dash where it's just a regular command-not-found, which doesn't stop the rest of the script from running. Of course keywords are part of syntax, whatever the guy in the answer first said. (There's some difference between keywords like [[ or if and operators like ( or ;, though.) Commented May 25, 2021 at 11:46

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.