6

The following script does not behave as I would have expected. Adding spaces around the '=' in the conditional made it perform how I wanted, but it got me thinking, what is it actually doing inside the conditional?

#!/bin/bash
S1='foo'
S2='bar'
if [ $S1=$S2 ];
then
    echo "S1('$S1') is equal to S2('$S2')
fi
echo $S1
echo $S2

The output is:

S1('foo') is equal to S2('bar')
foo
bar

The contents of S1 and S2 don't change from what they are assigned, so the = doesn't perform an assignment.

Cory Klein
  • 18,911

2 Answers2

12

It's helpful to remember that [ is actually a command, also available as test. In bash like in most other Bourne-like shells, it's a builtin command, so you can see the documentation with info bash [ or by searching inside man bash (also man builtin on some systems where bash is also the system's sh).

In that documentation:

test and [ evaluate conditional expressions using a set of rules based on the number of arguments.

  • 0 arguments
    The expression is false.
  • 1 argument
    The expression is true if and only if the argument is not null.
  • 2 arguments
    [...]
  • 3 arguments
    [...]

Pedantically speaking, when using [ instead of test for the command, the final (required) ] is also an argument, but that's not counted in the numbers above (which come straight from the bash documentation). Anyway...

The two-argument rules are various tests, and the three-argument ones are generally comparisons. When you put a space around the =, you get three arguments. But when you put it all together, you get one argument, and as you can see, if that argument isn't null, it returns true.

Well, technically, since you forgot the quotes around the parameter expansions in [ $S1=$S2 ] (and everywhere else), $S1 and $S2 are subject to split+glob, so that could result in more arguments passed to the [ command. In bash, like in ksh, that would even constitute a command injection vulnerability.

For instance, if $S1 was defined as S1='-v xx[$(reboot)] -o yy' and $S2 as S2='zz', the splitting part, with the default value of $IFS would yield -v, xx[$(reboot)], -o, yy=zz; [...] being a globbing operator the xx[$(reboot)] could expand to xxo, xxt if there were such files in the current directory, but if not, xx[$(reboot)] would be passed as-is to [ and -v array[index] in bash's [ builtin is a test to check whether the array element is set, and $(reboot) would be expanded to the output of the reboot command to determine the index.

The correct syntax is [ "$S1" = "$S2" ], where it's also critical not to forget the quotes.

mattdm
  • 40,245
5

The equals operator does nothing in this case.

The expression $S1=$S2 evaluates to an actual string, with the values of S1 and S2 in place, effectively the string literal foo=bar.

Since this string literal is not null, the statement

if [ "foo=bar" ];

evaluates to true, and the body of the if statement is executed.

ilkkachu
  • 138,973
Cory Klein
  • 18,911
  • I figured this one out right before submitting the question, but as the atmosphere of stackexchange is one of learning, I thought I may as well add my question and answer to the pool of knowledge that is stackexchange, rather than just discarding it. – Cory Klein Feb 18 '11 at 20:43
  • 3
    And what you're looking for is in fact [ "$S1" = "$S2" ]. Otherwise, if $S1 or $S2 contain wildcard or spaces, they would be expanded (e.g. try both with S1='a = a -o a' and S2='b'). In ksh, bash and zsh, you can use [[ $S1 = $S2 ]], because [[]] is special shell syntax whereas [ is an ordinary command with a funny name. Note that you also need spaces around the brackets in either case. – Gilles 'SO- stop being evil' Feb 18 '11 at 21:08
  • @Gilles'SO-stopbeingevil', in bash, like in ksh, that should be [[ "$S1" = "$S2" ]] or at least [[ $S1 = "$S2" ]]. [[ $S1 = $S2 ]] is a test of whether the contents of $S1 matches the pattern stored in $S2. For instance, S1='[x]'; [[ $S1 = $S1 ]] returns false because [x] doesn't match the [x] pattern. Quoting $S2 causes the contents to be take literally, not as a pattern. – Stéphane Chazelas Aug 27 '23 at 16:41