bash script: different results when called with or without sudo
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
?
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.
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:
-
./test.sh
No
sudo
; in the absence of a shebang line, your shell will try to execute the file itself. Since you are runningbash
, this will effectively runbash ./test.sh
and work. -
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 isbash
:$ grep root /etc/passwd root:x:0:0:root:/root:/bin/bash
-
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 isdash
and it fails sincedash
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.