10

In Ubuntu 16.04.3, I have a very simple bash script:

test.sh

[[ 0 == 0 ]] && result="true" || result="false"
echo $result
echo $USER $SHELL $0

When I call it as the non-root user me or as root, it works as expected. If I use sudo ./test.sh, it complains about a syntax error:

$ ./test.sh
true
me /bin/bash ./test.sh

$ sudo su
# ./test.sh 
true
root /bin/bash ./test.sh

# exit
$ sudo ./test.sh
./test.sh: 1: ./test.sh: [[: not found
false
root /bin/bash ./test.sh

What could be causing this? How can I fix it so that me can use this script both normally and with sudo?

  • 3
    Pro tip: there's no point in running sudo su. Just run sudo -i or sudo -s instead. – terdon Nov 25 '17 at 14:41
  • @terdon sudo -i changes the location to /root. sudo su or sudo -s don't change the directory location. – James Newton Nov 25 '17 at 15:00
  • Yes, read the question I linked to before for why. And sorry, I've edited my previous comment, I had forgotten to mention -s. – terdon Nov 25 '17 at 15:01
20

Every script begins with a Shebang, without it the shell starting your script doesn't know which interpreter should run your script1 and might – as in the case of sudo ./script.sh here – run it with sh, which in Ubuntu 16.04 is linked to dash. The conditional expression [[ is a bash compound command, so dash doesn't know how to handle it and throws the error you encountered.

The solution here is to add

#!/bin/bash

as the first line of your script. You may get the same result when you call it explicitly with sudo bash ./script.sh, but a shebang is the way to go.
To check which shell runs your script, add echo $0 to it. That's not the same as echo $SHELL, citing wiki.archlinux.org:

SHELL contains the path to the user's preferred shell. Note that this is not necessarily the shell that is currently running, although Bash sets this variable on startup.

1: As you started ./test.sh with bash it just assumed bash, the same goes for the sudo su subshell.

  • 1
    Also note that bash runs a script without shebang using bash, not /bin/sh. – muru Nov 25 '17 at 14:00
  • @dessert That fixes it. Thanks! How can I check from within a script which shell is running it? (echo $0 gives me the name of the script: ./test.sh) – James Newton Nov 25 '17 at 14:01
  • @JamesNewton no portable way, AFAIK, but you can check what /proc/$$/exe points to. Also you can test various variables like $BASH_VERSION, $ZSH_VERSION, etc. (but dash doesn't set any such variable) – muru Nov 25 '17 at 14:05
5

As @dessert explained, the problem here is that your script doesn't have a shebang line. Without a shebang, sudo will default to attempting to run the file using /bin/sh. I couldn't find it documented anywhere, but I confirmed by checking the sudo source code where I found the following in the file pathnames.h:

#ifndef _PATH_BSHELL
#define _PATH_BSHELL "/bin/sh"
#endif /* _PATH_BSHELL */

This means "set if the variable _PATH_BSHELL isn't defined, set it to /bin/sh". Then, in the configure script included in the source tarball, we have:

for p in "/bin/bash" "/usr/bin/sh" "/sbin/sh" "/usr/sbin/sh" "/bin/ksh" "/usr/bin/ksh" "/bin/bash" "/usr/bin/bash"; do
    if test -f "$p"; then
    found=yes
    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $p" >&5
$as_echo "$p" >&6; }
    cat >>confdefs.h <<EOF
#define _PATH_BSHELL "$p"
EOF

    break
    fi
done

This loop will look for /bin/bash, /usr/bin/sh, /sbin/sh, /usr/sbin/sh or /bin/ksh and then sets the _PATH_BSHELL to whichever was found first. Since /bin/sh was the first in the list and it exists, _PATH_BSHELL is set to /bin/sh. The result of all this is that the default shell of sudo unless otherwise defined is /bin/sh.

So, sudo will default to running things using /bin/sh and, on Ubuntu, that is a symlink to dash, a minimal POSIX compliant shell:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Feb 27  2015 /bin/sh -> dash

The [[ construct is a bash feature, it isn't defined by the POSIX standard and isn't understood by dash:

$ bash -c '[[ true ]] && echo yes'
yes
$ dash -c '[[ true ]] && echo yes'
dash: 1: [[: not found

In detail, in the three invocations you tried:

  1. ./test.sh

    No sudo; in the absence of a shebang line, your shell will try to execute the file itself. Since you are running bash, this will effectively run bash ./test.sh and work.

  2. sudo su followed by ./test.sh.

    Here, you are starting a new shell for the user root. This will be whatever shell is defined in the $SHELL environment variable for that user and, on Ubuntu, root's default shell is bash:

    $ grep root /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    
  3. sudo ./test.sh

    Here, you are letting sudo execute the command directly. Since its default shell is /bin/sh as explained above, this causes it to run the script with /bin/sh, which is dash and it fails since dash doesn't understand [[.


Note: the details of how sudo sets the default shell seem to be a bit more complex. I tried changing the files mentioned in my answer to point to /bin/bash but sudo was still defaulting to /bin/sh. So there must be some other places in the source code where the default shell is defined. Nevertheless, the main point (that sudo defaults to sh) still stands.

  • I couldn't find it documented anywhere – me neither, I just assumed it would use /bin/sh from the error message – what else could it possible be? The question is answered beautifully in What shell does sudo use · SO, see also man sudo, section COMMAND EXECUTION. Turns out sudo doesn't use an intermediate shell! – dessert Nov 25 '17 at 18:04
  • 1
    @dessert yes, it uses its own implementation of the execve system call which defaults to sh. And no, intermediate shells are irrelevant, this isn't about the shell that runs the command but about the shell interpreter used to read the shell script given. So no, it doesn't launch an intermediate shell but it still needs a shell interpreter for shell scripts. – terdon Nov 25 '17 at 18:09

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.