Why the zsh codes like [ $var == 'str' ] runs well as a command but error as a script file?

Viewed 157

The zsh_test.sh is simple, as following:

#!/usr/bin/env zsh

if [ $USER == 'root' ]; then
    echo "root"
else
    echo "not root"
fi
  • Copy and paste the above codes into a zsh shell, it executed well.

    $ #!/usr/bin/env zsh
    $ if [ $USER == 'root' ]; then
    then>     echo "root"
    then> else
    else>     echo "not root"
    else> fi                     
    not root
    
  • But directly execute script file zsh_test.sh, got an error.

    $ ./zsh_test.sh
    ./zsh_test.sh:3: = not found
    
2 Answers

I now see what's wrong: You are the victim of a fairly obscure zsh mechanism, which is described in the zshexpn man page and is called '=' expansion. From the man-page:

If a word begins with an unquoted `=' and the EQUALS option is set, the remainder of the word is taken as the name of a command. If a command exists by that name, the word is replaced by the full pathname of the command.

You can try it with the command

echo ==

which also outputs this error message. For instance, on my platofm

echo =ruby

outputs /usr/bin/ruby, because this is where I have ruby installed. If you would have in your PATH a program named =, the == would resolve to this path.

While it is unusual to use a double == sign inside [ ... ], the zsh implementation of this command allows it, but you would have to quote the operator, to avoid =-expansion:

if [ $USER '==' root ]; then

An alternative would be to use [[ ... ]] instead. This is not a command, but a syntactic construct, and expansion rules are different inside it. Therefore

if [[ $USER == root ]]; then

would work as well.

I'm afraid that you are using test command wrong. Let's see why.

test command is defined since Unix version III. You can often find this command also as [ binary in your PATH. In most modern shells (let's pretend that bash is modern shell as well), there is also implementation of test or [ as builtin command. From the specification, the only valid way to compare two strings is this:

       STRING1 = STRING2
              the strings are equal

       STRING1 != STRING2
              the strings are not equal

Original strict POSIX implementation of test command is somehow limited and can be difficult to use. But it is portable, and that it's main strength. But what if you don't care about portability at all? Then there are Conditional Expressions.

Conditional Expressions, available as [[ builtin command, are improved, not POSIX compatible replacement for original test command. Look in the manual for the things you can compare with them to get the idea. Double equality sign (==) is also supported (and the documentation explicitly says it's for compatibility with other sorts of computer language.)

Conclusion?

When you are writing scripts for particular shell, like zsh, and you are absolutely sure that portability is not important for you, always use [[ instead of [. Your life will be easier and change to your script is minimal:

#!/usr/bin/env zsh

if [[ $USER == 'root' ]]; then
    echo "root"
else
    echo "not root"
fi

If portability between different shells and environments is necessary, you will have to use original test or [ command, and forget about zsh, == and many other things at all.

#!/bin/sh

if [ "$USER" = 'root' ]; then
    printf '%s\n' "root"
else
    printf '%s\n' "not root"
fi
Related