bash脚本:使用或不使用sudo调用时的结果不同


10

在Ubuntu 16.04.3中,我有一个非常简单的bash脚本:

test.sh

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

当我将其称为非root用户me或as时root,它将按预期工作。如果使用sudo ./test.sh,它将抱怨语法错误:

$ ./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

是什么原因造成的?我该如何修复它,以便me可以正常使用该脚本并与它一起使用sudo


3
专家提示:跑步毫无意义sudo su。只是运行sudo -i还是sudo -s代替。
terdon

@terdon sudo -i将位置更改为/rootsudo susudo -s不要更改目录位置。
詹姆斯·牛顿,

是的,请阅读我之前链接的问题。抱歉,我已经忘记了提及我的先前评论-s
terdon

Answers:


20

每个脚本都以Shebang开头,没有它,启动脚本的外壳程序将不知道哪个解释程序应运行脚本1,并且可能(如此处所示)sudo ./script.sh使用with运行它sh,在Ubuntu 16.04中链接到dash。该条件表达式 [[是一个bash复合命令,所以dash不知道如何处理它,并抛出你遇到的错误。

解决方案是添加

#!/bin/bash

作为脚本的第一行。当您使用显式调用它时,您可能会得到相同的结果sudo bash ./script.sh,但是使用shebang是一种方法。
要检查哪个shell运行您的脚本,请添加echo $0该脚本。这 wiki.archlinux.org的echo $SHELL引用不同

SHELL包含用户首选shell的路径。请注意,尽管Bash在启动时设置了此变量,但这不一定是当前正在运行的shell。

1:当你开始./test.sh使用bash它只是假设bash,也是如此的sudo su子shell。


1
另请注意,bash使用bash而不是shebang来运行脚本,而无需shebang /bin/sh
muru

@dessert修复它。谢谢!如何从脚本中检查正在运行哪个shell?(echo $0给我的脚本的名称:./test.sh
詹姆斯·牛顿

@JamesNewton没有可移植的方式,AFAIK,但是您可以检查/proc/$$/exe指向什么。您也可以测试各种变量,例如$BASH_VERSION$ZSH_VERSION等(但破折号未设置任何此类变量)
muru

5

正如@dessert所解释的,这里的问题是您的脚本没有shebang行。如果没有shebang,sudo则默认为尝试使用运行文件/bin/sh。我在任何地方都找不到文档,但通过检查sudo在文件中找到以下内容的源代码来确认pathnames.h

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

这意味着“如果_PATH_BSHELL未定义变量,则将其设置为/bin/sh”。然后,在configure源压缩包中包含的脚本中,我们有:

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

这个循环会寻找/bin/bash/usr/bin/sh/sbin/sh/usr/sbin/sh/bin/ksh然后设置_PATH_BSHELL取其首次发现。由于/bin/sh是列表中的第一个,并且存在,_PATH_BSHELL因此设置为/bin/sh。所有这一切的结果是sudo除非另外定义,否则默认外壳程序为/bin/sh

因此,sudo默认情况下将使用/bin/shUbuntu 运行,并且在Ubuntu上是与dashPOSIX兼容的最小外壳的符号链接:

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

[[构造是bash的功能,它不是POSIX标准定义的,也不为dash

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

详细地说,您尝试了以下三种调用:

  1. ./test.sh

    sudo; 在没有shebang行的情况下,您的shell将尝试执行文件本身。由于您正在运行bash,因此可以有效地运行bash ./test.sh和运行。

  2. sudo su其次是./test.sh

    在这里,您正在为user启动一个新的shell root。这将是在该$SHELL用户的环境变量中定义的任何shell ,在Ubuntu上,root的默认shell是bash

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

    在这里,您可以sudo直接执行命令。由于其默认外壳程序/bin/sh如上所述,因此这将导致它使用来运行脚本/bin/sh,并且脚本dash失败,因为dash不理解[[


注意:如何sudo设置默认Shell 的细节似乎更加复杂。我尝试将答案中提到的文件更改为,/bin/bashsudo仍默认为/bin/sh。因此,在源代码中必须存在定义默认Shell的其他位置。尽管如此,要点(sudo默认为sh)仍然存在。


我在任何地方都找不到它的文档 -我也没有,我只是假设它会/bin/sh在错误消息中使用-还有什么可能?sudo使用什么外壳·SO可以很好地回答问题,另请参见“ man sudo命令执行”部分。原来sudo不使用中间壳
甜点

1
@dessert是,它使用自己的execve系统调用实现,默认为sh。不,中间外壳无关紧要,这与运行命令的外壳无关,而与用于读取给定外壳脚本的外壳解释器有关。因此,不,它不会启动中间shell,但仍需要用于shell脚本的shell解释器。
terdon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.