4

I was demonstrating a piece of funny code (or so I think) to my students in class, and to my surprise, it did not work in zsh. I then switched to bash and it worked as expected.

The code is the following:

[ $(( $RANDOM % 6 )) == 0 ] && echo "Boom" || echo "Click"

In bash it prints Boom or Click randomly, as expected. But in zsh I get the error message below:

zsh: = not found

Can anyone explain what is the difference? To my understanding, both shells should interpret scripts in an equivalent manner.

Thanks.

  • Since it's an integer comparison, wouldn't it be better practice to use -eq anyhow? Or perhaps use the arithmetic expression directly i.e. (( $RANDOM % 6 == 0 )) && echo "Boom" || echo "Click" – steeldriver Jan 13 at 18:36
  • @steeldriver And, since it is an aritmetic expression, the $ before the variable name could (should?) be removed. And, since in C the result of an arithmetic expression is a valid result which (if 0) will generate an exit code of 1 (0 otherwise). Then: ((RANDOM%6)) && echo Click || echo Boom should be plenty. – Isaac Jan 13 at 18:50
  • 2
    Same question on the zsh mailing list a few days ago: zsh.org/mla/users/2020/msg00060.html. See my answer there. – Stéphane Chazelas Jan 14 at 7:34
10

Try doing unsetopt equals and try again. Or put quotes on the first equals character.

If you use [ for conditions, all arguments are evaluated normally and = at the start of a word is a useful zsh shorthand to get the location of a command. e.g. =ls expands to /bin/ls. Given == zsh looks for an = command and doesn't find one.

Zsh in posix mode (sh emulation) will be a lot more compatible with bash. Many things are compatible and most learnt knowledge of bash is still useful but there are certain things to be aware of if you want to write portable scripts.

Unlike [ which is a builtin, [[ is a reserved word understood by the parser and will give you fewer issues. However in this case you're using arithmetic mode so you simplify it:

    (( RANDOM % 6 == 0 )) && echo "Boom" || echo "Click"

In fact the comparison to zero is even superfluous because, the default return status reflects whether the value is zero, though the sense is reversed:

    (( RANDOM % 6 )) && echo "Click" || echo "Boom"
  • So, the expansion of an unquoted argument that starts with an = is a zedshism. And, to be avoided it needs to be quoted. There goes away the concept of (by default) avoiding quotes in zsh. – Isaac Jan 13 at 21:59
  • You may want to give credit to the user who said all about arithmetic in your post in a comment. – Isaac Jan 13 at 22:01
  • 1
    Also note that the standard equality comparison operator for the [ utility is =, not ==. Also, in bash (and other POSIX shells but not zsh even in sh emulation), arithmetic expansion undergoes word splitting, so should be quoted like all other word expansions. So for code compatible to zsh, bash and other POSIX shells, that should rather be [ "$(( RANDOM % 6 ))" = 0 ]. = (and == where available) is for string comparison. For integer comparison, it's -eq. Note that $RANDOM is not POSIX, it's a kshism supported by bash and zsh as well. – Stéphane Chazelas Jan 14 at 7:23
  • 1
    @Isaac, all shell special characters need to be quoted when you want to pass them literally. That goes for a leading = but also for *, <, |, space, etc. A standalone = like a standalone [ above don't need to be quoted as a special case (like a standalone ! in both bash and zsh). The thing zsh fixed compared to other shells is that unquoted word expansions don't undergo split+glob so do not always need to be quoted (cmdsubst still undergoes split and all word expansions undergo empty removal). (having said that, I agree that =cmd, though a useful feature, is a poor API) – Stéphane Chazelas Jan 14 at 7:27
  • @StéphaneChazelas (1) You must (at the very minimum) agree that even the shortest of the quoted operator requires that the fingers press an additional key. A == is never equal to a \==. (2) You may try to convince my fingers that they are equivalent and that they represent the same idea but I am sure that you will fail to convince them. (Cont.)... – Isaac Jan 14 at 19:56
-1

tl;dr table

(Y=true, -=false, ¤=failure) (correct test should be Y -):

                     | dash  ksh   bash  zsh   
  test a  =  "$b"    | Y -   Y -   Y -   Y -    
     [ a  =  "$b" ]  | Y -   Y -   Y -   Y -    
    [[ a  =  "$b" ]] | ¤ ¤   Y -   Y -   Y -    
  test a  == "$b"    | ¤ ¤   Y -   Y -   - -    
     [ a  == "$b" ]  | ¤ ¤   Y -   Y -   - -    
    [[ a  == "$b" ]] | ¤ ¤   Y -   Y -   Y -    
  test a \=  "$b"    | Y -   Y -   Y -   Y -    
     [ a \=  "$b" ]  | Y -   Y -   Y -   Y -    
    [[ a \=  "$b" ]] | ¤ ¤   Y -   - -   - -    
  test a \== "$b"    | ¤ ¤   Y -   Y -   Y -    
     [ a \== "$b" ]  | ¤ ¤   Y -   Y -   Y -    
    [[ a \== "$b" ]] | ¤ ¤   Y -   - -   - -

The issue has two aspects.

1- A == should be used inside [[…]], not in simpler […] or test.

2- In some cases, in zsh a == needs to be quoted as an initial = is special and activates a =cmd expansion.


A Conditional Expression could be written inside a test …, a […] or a [[…]]. But only inside [[…]] the processing is special and therefore a == cause no problem.

There are several possible (string) equality tests:

'test a  =  "$b"   '
'   [ a  =  "$b" ] '
'  [[ a  =  "$b" ]]'

'test a  == "$b"   '
'   [ a  == "$b" ] '
'  [[ a  == "$b" ]]'

'test a \=  "$b"   '
'   [ a \=  "$b" ] '
'  [[ a \=  "$b" ]]'

'test a \== "$b"   '
'   [ a \== "$b" ] '
'  [[ a \== "$b" ]]'

And that could be tested in several shells (i.e.: dash ksh bash zsh).

If b=a the tests should be true (indicated by Y),
and if b=b the tests should be false (indicated by -),
failure (anything other than an exit code of 0 or 1) is indicated by ¤.

Only the first two work in all shells from above.

                     | dash  ksh   bash  zsh   
  test a  =  "$b"    | Y -   Y -   Y -   Y -    
     [ a  =  "$b" ]  | Y -   Y -   Y -   Y -  

And there are four alternatives (partial list) that fail in some shell:

                     | dash  ksh   bash  zsh   
  test a  == "$b"    | ¤ ¤   Y -   Y -   - -    
     [ a  == "$b" ]  | ¤ ¤   Y -   Y -   - -    
    [[ a \=  "$b" ]] | ¤ ¤   Y -   - -   - -    
    [[ a \== "$b" ]] | ¤ ¤   Y -   - -   - - 

In dash (legacy shells), tests fail because the shells don't recognize [[ or == or the quoted \== on each case.

Both unquoted == fail in zsh (not inside [[…]]) because they are treated (and expanded) as a normal argument and arguments starting with = have an special meanning in zsh (=cmd expands to the path to cmd).

$ zsh -c 'test "a" == "a"'
zsh:1: = not found

$ zsh -c '[ "a" == "a" ]'
zsh:1: = not found

A quoted double = (\==) fails both in zsh and in bash:

$ zsh -c '[[ "a" \== "a" ]]'
zsh:1: condition expected: \==

$ $ bash -c '[[ "a" \== "a" ]]'
bash: -c: line 0: conditional binary operator expected
bash: -c: line 0: syntax error near `\=='
bash: -c: line 0: `[[ "a" \== "a" ]]'

So, the final table is (Y=true, -=false, ¤=failure) is:

                     | dash  ksh   bash  zsh   
  test a  =  "$b"    | Y -   Y -   Y -   Y -    
     [ a  =  "$b" ]  | Y -   Y -   Y -   Y -    
    [[ a  =  "$b" ]] | ¤ ¤   Y -   Y -   Y -    
  test a  == "$b"    | ¤ ¤   Y -   Y -   - -    
     [ a  == "$b" ]  | ¤ ¤   Y -   Y -   - -    
    [[ a  == "$b" ]] | ¤ ¤   Y -   Y -   Y -    
  test a \=  "$b"    | Y -   Y -   Y -   Y -    
     [ a \=  "$b" ]  | Y -   Y -   Y -   Y -    
    [[ a \=  "$b" ]] | ¤ ¤   Y -   - -   - -    
  test a \== "$b"    | ¤ ¤   Y -   Y -   Y -    
     [ a \== "$b" ]  | ¤ ¤   Y -   Y -   Y -    
    [[ a \== "$b" ]] | ¤ ¤   Y -   - -   - -

Additionally, As I said in this comment

Since what you are performing is an arithmetic expression:

[ $(( $RANDOM % 6 )) == 0 ]
  1. Since an IFS could have numbers, the Arithmetic expansion should be quoted:

    [ "$(( $RANDOM % 6 ))" == 0 ]
    
  2. The $ before the variable name isn't needed inside an Arithmetic Expression.

    [ "$(( RANDOM % 6 ))" == 0 ]
    
  3. An equivalent arithmetic expression (in ksh, bash, zsh) is:

    (( RANDOM % 6 == 0 ))
    

    Which avoids quoting, and the Conditional as it sets the exit code directly.

    4- And, Since in C the result of an arithmetic expression is a valid true or false result which (if 0) will generate an exit code of 1 (0 otherwise). Then:

    (( RANDOM%6 )) && echo Click || echo Boom
    

should be plenty.

Understand that precedence of operators in zsh arithmetic is different than most other shells. Find C_PRECEDENCES in the manual for an expanded description. A zedshism.

  • The Heirloom Bourne and Almquist shells do not support == in test. But the Z shell actually does. – JdeBP Jan 13 at 21:34
  • @JdeBP No, plain == fails in zsh in both test and […]. Quoted \== work but fails in [[…]]. Understand the table. – Isaac Jan 25 at 21:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.