tommyc38
tommyc38

Reputation: 741

Default test expression behaves different in zsh vs bash - why?

Here is a simple test case script which behaves differently in zsh vs bash when I run with $ source test_script.sh from the command line. I don't necessarily know why there is a difference if my shebang clearly states that I want bash to run my script other than the fact that the which command is a built-in in zsh and a program in bash. (FYI - the shebang directory is where my bash program lives which may not be the same as yours--I installed a new version using homebrew)

#!/usr/local/bin/bash
if [ "$(which ls)" ]; then
    echo "ls command found"
else 
    echo "ls command not found"
fi

if [ "$(which foo)" ]; then
    echo "foo command found"
else
    echo "foo command not found"

I run this script with source ./test-script.sh from zsh and Bash.

Output in zsh:

ls command found
foo command found

Output in bash:

ls command found
foo command not found

My understanding is that default for test or [ ] (which are the same thing) evaluate a string to true if it's not empty/null. To illustrate:

zsh:

$ which foo
foo not found

bash:

$ which foo

$

Moreover if I redirect standard error in zsh like:

$ which foo 2> /dev/null
foo not found

zsh still seems to send foo not found to standard output which is why (I am guessing) my test case passed for both under the zshell; because the expansion of "$(which xxx)" returned a string in both cases (e.g. /some/directory and foo not found (zsh will ALWAYS return a string?).

Lastly, if I remove the double quotes (e.g. $(which xxx)), zsh gives me an error. Here is the output:

ls command found
test_scritp.sh:27: condition expected not:

I am guessing zsh wanted me to use [ ! "$(which xxx)" ]. I don't understand why? It never gave that error when running in bash (and isn't this supposed to run in bash anyway?!).

Why isn't my script using bash? Why is something so trivial as this not working? I understand how to make it work fine in both using the -e option but I simply want to understand why this is all happening. Its driving me bonkers.

Upvotes: 2

Views: 1695

Answers (1)

tripleee
tripleee

Reputation: 189387

There are two separate problems here.

First, the proper command to use is type, not which. Like you note, the command which is a zsh built-in, whereas in Bash, it will execute whatever which command happens to be on your system. There are many variants with different behaviors, which is why POSIX opted to introduce a replacement instead of trying to prescribe a particular behavior for which -- then there would be yet one more possible behavior, and no way to easily root out all the other legacy behaviors. (One early common problem was with a which command which would examine the csh environment, even if you actually used a different shell.)

Secondly, examining a command's string output is a serious antipattern, because strings differ between locales ("not found" vs. "nicht gefunden" vs. "ei löytynyt" vs. etc etc) and program versions -- the proper solution is to examine the command's exit code.

if type ls >/dev/null 2>&1; then
    echo "ls command found"
else
    echo "ls command not found"
fi

if type foo >/dev/null 2>&1; then
    echo "foo command found"
else
    echo "foo command not found"
fi

(A related antipattern is to examine $? explicitly. There is very rarely any need to do this, as it is done naturally and transparently by the shell's flow control statements, like if and while.)

Regarding quoting, the shell performs whitespace tokenization and wildcard expansion on unquoted values, so if $string is command not found, the expression

[ $string ]

without quotes around the value evaluates to

[ command not found ]

which looks to the shell like the string "command" followed by some cruft which isn't syntactically valid.

Lastly, as we uncovered in the chat session (linked from comments) the OP was confused about the precise meaning of source, and ended up running a Bash script in a separate process instead. (./test-script instead of source ./test-script). For the record, when you source a file, you cause your current shell to read and execute it; in this setting, the script's shebang line is simply a comment, and is completely ignored by the shell.

Upvotes: 5

Related Questions