2

Here's an example simple shell script:

#!/usr/bin/env bash
if [ 1 == 1 ]; then
echo "Something"
fi

When I run this

sh ./test.sh

I get:

./test.sh: 4: ./test.sh: Syntax error: "fi" unexpected (expecting "then")

It doesn't seem to recognize the 'then' I have in there. Any clue as to why? This is Ubuntu for Windows in case that part matters.

Running shell scripts in general is not a problem, it just seems to struggle with if/else.

Zanna
  • 70,465

2 Answers2

6

There are two problems here. First, while you have a shebang line calling bash, you are executing the script as sh script.sh. The shebang line already specifies an interpreter for this script. So, just make the script executable:

chmod a+x ./test.sh

And then run it:

./test.sh

Alternatively, run it with bash explicitly:

bash ./test.sh

That will make it run with bash instead of sh which is a more limited shell and doesn't understand ==. Which brings us to the second problem and why this fails. Strangely enough, you want eq or =, not ==. The operator that tests numerical equality in POSIX shell scripts (sh is a POSIX-compliant shell) is -eq while the one that tests string equality is = and == isn't a thing.

So, if you want your script to be run by sh, change it to:

if [ 1 = 1 ]; then
    echo "Something"
fi

Which checks that the string 1 is the same as the string 1, or to compare numerically, use:

if [ 1 -eq 1 ]; then
    echo "Something"
fi

In addition, you have another problem. I can only reproduce the specific error you get in dash if I add a \r after the then. This is a classic problem when moving between Windows and Linux. While Linux uses \n as the line end character, Windows ends its lines with \r\n. This \r character is not visible to the user, but it is there and confuses the script. So, you can remove it with:

sed -i 's/\r//' ./test.sh

And in the future, use a proper text editor to write your scripts. Either one that works in the WSL system, or one that can at least be configured to not add \r to the ends of its lines.

terdon
  • 100,812
  • 1
    However, this doesn't explain the error OP gets (dash says foo.sh: 2: [: 1: unexpected operator) – muru Nov 16 '17 at 09:58
  • 1
    @muru no, I am assuming that's some strange foible of the sh in WSL. Either way, the above solutions should fix the error. – terdon Nov 16 '17 at 10:04
  • 1
    I have used WSL a fair few times (usually for testing commands when on Windows), and I haven't yet found a significant difference in behaviour between the Linux commands there and on Ubuntu proper (not for the shells, anyway). I'd imagine something else, maybe Windows line endings or something like that. – muru Nov 16 '17 at 10:16
  • 1
    @muru yeah, I thought of Windows line endings, but I passed the OP's script (which I assumed was directly pasted) through od and didn't see any. But you're right, adding a \r after then reproduces the error. Answer edited, thanks. – terdon Nov 16 '17 at 10:33
  • If it is \r , I would point out you should not currently be editing files for your linux environment from your windows environment - see - https://blogs.msdn.microsoft.com/commandline/2016/11/17/do-not-change-linux-files-using-windows-apps-and-tools/ – Panther Nov 16 '17 at 12:37
  • It is a CR. You can get the same error by opening a Bash shell and running sh -c $'if [ 1 == 1 ]; then\r; echo "Something"\r; fi' – wjandrea Nov 17 '17 at 23:45
2

Let's keep it simple:

  • sh is not bash on Ubuntu (see this for reference).

  • [ is a command, same as test command. It doesn't support == for arithmetic comparison. Use -eq instead. This is one of utilities specified by POSIX standards, hence works in both bash and dash.

    if [ 1 -eq 1 ];
    then
        echo "it works!"
    fi
    
  • bash has (( which is called arithmetic expansion. This supports ==. This feature is apparently borrowed from ksh shell.

    $ var=25 bash -c 'if((var==5)); then echo Y;else echo "N";fi'      
    N
    $ var=25 bash -c 'if((var==25)); then echo Y;else echo "N";fi' 
    Y
    
  • Despite the fact that dash doesn't support if (()), in either shell $(( will work because it is mandated by POSIX standard. Taking only features that are common to both shells, we could do something like this while still using == for comparisons:

    
    $ var=25 dash -c 'if [ $((var==25)) -eq 1 ];then echo Y; else echo N;fi'
    Y
    $ var=25 dash -c 'if [ $((var==5)) -eq 1 ];then echo Y; else echo N;fi' 
    N
    
  • We could throw away if statement altogether and use case instead(note that return status conforms to C, not shell behavior, i.e. true is 1 and false is 0, not the other way around as in shell):

    $ dash -c 'case $((1==1)) in 1) echo "equal";; 0) echo "not equal";;esac'                                                                                                
    equal
    $ dash -c 'case $((1==2)) in 1) echo "equal";; 0) echo "not equal";;esac'                                                                                                
    not equal
    
  • or we can get sneaky with (ab)using arithmetic expansion:

    $ var=25 dash -c '_0(){ false;}; _1(){ true;}; if "_$((var==25))" ; then echo Y; else echo N; fi'
    
Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497