为什么我不能在Shell脚本中使用'sudo su'命令?如何让Shell脚本自动以sudo权限运行?

18

我无法确定这个问题出在哪里。当我在终端运行它并输入密码时,什么也没有发生,但是如果我在终端中分别运行每个命令,它就可以正常工作。谢谢!

#!/bin/bash    

sudo su;
mkdir /opt/D3GO/;
cp `pwd`/D3GO /opt/D3GO/;
cp `pwd`/D3GO.png /opt/D3GO/;
cp `pwd`/D3GO.desktop /usr/share/applications/;
chmod +x /opt/D3GO/D3GO

1
顺便提一下,"$PWD"$(pwd)或其反引号等效形式更加高效;每次运行命令替换时,都会fork()出一个子进程,在该单独的进程中运行给定的命令(在本例中为pwd),然后通过管道读取其输出;而$PWD直接在父shell中计算。 - Charles Duffy
请参考以下网址:https://dev59.com/g1oU5IYBdhLWcg3wTVzE - tripleee
6个回答

21
您可以使用Here Documents将输入重定向到交互式shell脚本。 运算符<<是一条指令,用于读取输入,直到找到包含指定定界符的行,如EOF(文件结尾)。
sudo su <<EOF
echo "code"
EOF

例如

#!/bin/bash    
sudo su <<EOF
mkdir /opt/D3GO/
cp `pwd`/D3GO /opt/D3GO/
cp `pwd`/D3GO.png /opt/D3GO/
cp `pwd`/D3GO.desktop /usr/share/applications/
chmod +x /opt/D3GO/D3GO
EOF

19

命令sudo su可以启动一个交互式的root shell,但它不会将当前shell转换为root shell。

要做你想做的事情的惯用语是这样的(感谢@CharlesDuffy的额外小心):

#check for root
UID=$(id -u)
if [ x$UID != x0 ] 
then
    #Beware of how you compose the command
    printf -v cmd_str '%q ' "$0" "$@"
    exec sudo su -c "$cmd_str"
fi

#I am root
mkdir /opt/D3GO/
#and the rest of your commands

该想法是检查当前用户是否为root,如果不是,则使用su重新运行相同的命令。


2
"$0 $@" 这种写法是不正确的,因为它试图将 $@(一个数组)压缩成一个字符串,这会在空格、通配符等方面出现严重问题。更好的写法是 printf -v cmd_str '%q ' "$0" "$@"; exec sudo su -c "$cmd_str" - Charles Duffy
1
顺便说一下,[ x$foo = xbar ] 这种写法在任何符合 POSIX.2 标准的 shell 中都是不必要的 -- 它是 Bourne 时代的遗留物 -- 但如果你想要一些与正确性相关的偏执症,你应该真正地加上引号:[ "$UID" != 0 ];否则,即使有了 x,如果你扩展后的字符串分成多个单词,你仍然会得到语法错误... 即使对于 $UID,如果你的 IFS 包含数字字符,这种情况也可能发生。 - Charles Duffy
1
@rodrigo,你确定需要这些 x 吗?如果你不是在引用(毕竟,在这里你没有引用),你会发现空字符串的情况下会出现错误...但是只添加 x 只能修复空字符串的情况,而添加引号则可以修复空字符串和多个单词的情况。 - Charles Duffy
1
@rodrigo,不会的。printf %q负责发出一个字符串,该字符串可以安全地按原样评估。 - Charles Duffy
2
做得好,充分利用了bash的功能,并用sudo -s替换了sudo su -c,这个解决方案可以简化为[[ $(id -u) -eq 0 ]] || exec sudo -s "$BASH_SOURCE" $(printf '%q ' "$@") - mklement0
显示剩余10条评论

10

sudo su不是在shell内运行的命令--它会启动一个新的shell

这个新的shell已经不再运行你的脚本了,而原来正在运行脚本的旧的shell会等待新的shell退出才会继续执行。


用户是否可以被提示输入密码呢? - Dusan Milosevic
sudosu通常支持直接在TTY上提示。 - Charles Duffy

9

被接受的答案很好,但是使用sudo重新调用脚本的习语可以简化并使其更加通用

[[ $(id -u) -eq 0 ]] || exec sudo /bin/bash -c "$(printf '%q ' "$BASH_SOURCE" "$@")"
  • 使用[[ ... ]]代替[ ... ]使得不需要在操作数前添加x(或在左操作数加双引号)。

  • 使用bash -c代替su -c来解释重构后的命令行使得命令更加便携,因为并非所有平台都支持su -c(例如macOS不支持)。

  • bash中,$BASH_SOURCE通常是更可靠的方式来指向正在运行的脚本。


使用上述方法,参数中的任何变量引用或命令/算术替换都会被调用shell自动扩展。

如果你反而想要延迟扩展——这样变量引用直到sudo shell以root用户的上下文运行才扩展——请使用以下方式:

(( __reinvoked )) || exec sudo -s __reinvoked=1 "$BASH_SOURCE" "$@"

请注意,您需要在包含变量引用或命令替换的任何参数上使用单引号以进行延迟扩展;例如, '$USER'.
请注意使用特定环境变量 __reinvoked 确保重新调用一次(即使最初已作为root用户调用)。
下面是一个演示第一种技术的示例脚本
  • 如果未作为root调用脚本,则使用sudo -s重新调用自身,并将所有参数原样传递。

  • 除非先前经过身份验证并仍处于超时期间,否则 sudo 将提示输入管理员密码。

#!/bin/bash

[[ $(id -u) -eq 0 ]] || exec sudo /bin/bash -c "$(printf '%q ' "$BASH_SOURCE" "$@")"

# Print the username and all arguments.
echo "Running as: $(id -un)"
echo "Arguments:"
for arg; do echo "  $((++i)): [$arg]"; done

acfreitas的有用回答演示了一种“脚本内嵌脚本”的技术,其中使用here-document通过stdin提供shell代码给sudo su
同样,sudo -s已足够,并且引号很重要

sudo -s -H <<'EOF'
echo "$HOME"
EOF

请注意,这里文档的开头分隔符,在本例中为EOF,被“引用”,以防止文档内容被“当前”shell直接解释。如果您没有引用(任何部分)EOF$HOME将被扩展为“当前”用户的主目录。
如果您想要混合“前置”和“延迟”扩展,请将开头的文档分隔符保持未引用状态,并有选择性地对$进行\转义。
sudo -s -H <<EOF
echo "Called by: $USER; root's home dir: \$HOME"
EOF

2

sudo su会尝试作为root启动一个新的shell。 一旦打开了这个新的shell,原始脚本将不会继续执行,直到新的shell被关闭。

要修复此问题,请尝试:

在shell脚本中尝试:

su <username> -c "my command"

所以如果用户是"userA":
su userA -c "mkdir /opt/D3GO/"

然而,如果你是用户A并且想要以root身份运行脚本的一部分,系统会提示你输入密码。

su root -c "mkdir /opt/D3GO/"

你也可以通过在一开始就使用sudo运行脚本来解决这个问题。

sudo ./myScript.sh

这样脚本就会保留原始用户,您可以使用标准变量(如${USERNAME}${UID}等)来访问它。

取决于哪种方式对您更有效。


脚本中的 su root 命令也为我打开了密码提示。 - Don Cheadle

2

运行“sudo su”会打开一个新的shell并且该命令直到您退出该shell后才返回。也许可以将脚本拆分成两个文件:第一个运行sudo并在sudo下执行第二个脚本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接