51

I tried the following script

#!/bin/bash
var1="Test 1" 
var2="Test 2"
if [ "$var1"="$var2" ] 
  then 
    echo "Equal" 
  else 
    echo "Not equal"
fi

It gave me Equal. Although it should have printed Not equal

Only when I inserted space around = it worked as intended

if [ "$var1" = "$var2" ] 

and printed Not equal

Why is it so? Why "$var1"="$var2" is not same as "$var1" = "$var2"?

Moreover, when I wrote if [ "$var1"= "$var2" ], it gave

line 4: [: Test 1=: unary operator expected

What does it it mean? How come its expecting unary operator?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Andrew-Dufresne
  • 5,464
  • 7
  • 46
  • 68
  • If you do a `test STRING` or (equivalently `[ STRING ]`), this always evaluates to true (see _man test_). `"$var1"="$var2"` is obviously a single string, while `"$var1" = "$var2" ` are three arguments, in which case the middle argument is taken as operator. When you do a `[ "$var1"= "$var2" ]`, you are passing two arguments, and in this case, the first argument must be an unary operator - for instance `-n`. This is also explained in _man test_. – user1934428 Apr 28 '23 at 07:21

4 Answers4

83

test (or [ expr ]) is a builtin function. Like all functions in bash, you pass its arguments as whitespace separated words.

As the man page for bash builtins states: "Each operator and operand must be a separate argument."

It's just the way bash and most other Unix shells work.

Variable assignment is different.

In bash a variable assignment has the syntax: name=[value]. You cannot put unquoted spaces around the = because bash would not interpret this as the assignment you intend. bash treats most lists of words as a command with parameters.

E.g.

# call the command or function 'abc' with '=def' as argument
abc =def

# call 'def' with the variable 'abc' set to the empty string
abc= def

# call 'ghi' with 'abc' set to 'def'
abc=def ghi

# set 'abc' to 'def ghi'
abc="def ghi"
terdon
  • 3,260
  • 5
  • 33
  • 57
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 1
    Thanks Charles Bailey. There is one thing more, as man page says "each operator and ...." then why `var = "test" ;` does not work? To make it work, I have to remove spaces `var="test";` Is assignment `=` is not an operator? – Andrew-Dufresne Feb 12 '11 at 10:56
  • 4
    That man page applies to the `test` builtin function. In bash a variable assignment is part of the shell grammar, it's not a function. It requires no spaces. – CB Bailey Feb 12 '11 at 11:02
  • 1
    ...and 'abc = def' invokes the command abc with two arguments '=' and 'def' – William Pursell Feb 13 '11 at 11:53
8

When the shell reads

if [ "$var1" = "$var2" ]

it invokes the command [ with 4 arguments. Whether [ is a builtin or an external command is irrelevant, but it may help to understand that it may be the external command /bin/[. The second argument is the literal '=' and the fourth is ']'. However, when the shell reads

if [ "$var1"= "$var2" ]

[ only gets 3 arguments: the expansion of $var1 with '=' appended, the expansion of $var2, and ']'. When it gets only 3 arguments, it expects the last argument to be ']' and the first argument to be a unary operator.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
3

To add to the existing explanation, "$var1"="$var2" is just a single non-empty string, and thus always evaluates as true in a conditional.

[ "$var1"="$var2" ] && echo true

The above command will always print out true (even if var1 and var2 be empty).

Jahid
  • 21,542
  • 10
  • 90
  • 108
-2

In bash the best is to use [[ ]]:

x="test"
y="test"
if [[ "${x}" = "${y}" ]]; then
    echo "Equals"
else
    echo "No equals"
fi
knesenko
  • 72
  • 2
  • 3
    Bash`[[` is governed by the same shell syntax as all other constructs. Switching to `[[` would not solve the OP's problem. As to whether this is "better" depends on whether you prioritize convenience over portability. If you are committed to Bash-only, it's probably true. – tripleee Aug 14 '15 at 06:32
  • As I wrote, I meant **bash**. Of course if you want your code to be portable, you should use **/bin/sh** – knesenko Aug 15 '15 at 06:02
  • 6
    Still, this does not attempt to address the OP's question, and should be posted as a comment instead (if at all). – tripleee Aug 15 '15 at 07:09
  • @knesenko you should read better both questions and comments... – linuxfan says Reinstate Monica Mar 23 '22 at 16:13