【问题标题】:Why can't I use 'sudo su' within a shell script? How to make a shell script run with sudo automatically为什么我不能在 shell 脚本中使用“sudo su”?如何使用 sudo 自动运行 shell 脚本
【发布时间】:2014-07-08 19:36:45
【问题描述】:

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

#!/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

【问题讨论】:

  • 顺便说一句,"$PWD"$(pwd) 或其等效的反引号评估效率更高;每次运行命令替换时,您都在fork()关闭子进程,在该单独进程中运行给定命令(在本例中为pwd),然后通过管道读取其输出;而 $PWD 直接在父 shell 中进行评估。

标签: linux bash shell sudo


【解决方案1】:

命令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重新运行同样的命令

【讨论】:

  • "$0 $@" 是不对的——因为它试图将$@(一个数组)扁平化为一个字符串,所以它会被空格、全局字符等严重破坏。 printf -v cmd_str '%q ' "$0" "$@"; exec sudo su -c "$cmd_str" 会更近。
  • (顺便提一句——[ x$foo = xbar ] 实践在任何 POSIX.2 兼容的 shell 中都不需要——这是 Bourne 时代的坚持——但如果你想要一些与正确性相关的偏执狂,您应该真正引用:[ "$UID" != 0 ];否则,即使使用xs,如果您的扩展字符串拆分为多个单词,您也会遇到语法错误......如果您的$UID IFS 包含数字字符)。
  • @rodrigo,你确定xs 真的需要吗?如果你没有引用(毕竟,你没有在这里引用),你会在空字符串的情况下看到错误......但添加 xs only 修复空字符串大小写,而添加引号可以修复空字符串大小写和多词大小写。
  • @rodrigo,不,不会。 printf %q 负责发出一个可以按原样安全评估的字符串。
  • 干得好;充分利用bash的特性,将sudo su -c替换为sudo -s,解决方案可以简化为[[ $(id -u) -eq 0 ]] || exec sudo -s "$BASH_SOURCE" $(printf '%q ' "$@")
【解决方案2】:

您可以使用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

【讨论】:

    【解决方案3】:

    sudo su 不是在 shell 中运行的命令——它启动一个新的 shell

    新的 shell 不再运行您的脚本,而 正在运行脚本的旧 shell 等待新的 shell 退出后再继续。

    【讨论】:

    • 那么可以提示用户输入密码吗?
    • sudosu 通常支持直接向 TTY 提示。
    【解决方案4】:

    accepted answer 运行良好,但 脚本使用 sudo 按需重新调用自身的惯用语 可以简化 并使更便携

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

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

      李>
    • bash中,$BASH_SOURCE通常是引用运行脚本的更可靠的方式。


    使用上述方法,参数中的任何变量引用或命令/算术替换总是由 调用 shell 扩展。

    如果您想要 延迟 扩展 - 这样变量引用不会在 sudo shell 运行之前在 root 的上下文中扩展 用户 - 使用这个:

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

    请注意,您必须单个引用包含变量引用或命令替换的任何参数,以便它们被延迟扩展;例如,'$USER'

    注意使用 ad-hoc 环境变量 __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's helpful answer 演示了一种“script-inside-a-script”技术,其中here-document 用于通过stdin 向sudo su 提供shell 代码。
    同样,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
    

    【讨论】:

      【解决方案5】:

      因为运行“sudo su”会打开一个新的 shell,并且在您退出该 shell 之前该命令不会返回。也许将脚本分成 2 个文件:第一个运行 sudo 并在 sudo 下执行第二个脚本。

      【讨论】:

        【解决方案6】:

        sudo su 将尝试以 root 身份启动一个新的 shell。 一旦打开了新的 shell,原始脚本将不会继续,直到新的 shell 关闭。

        尝试修复:

        在shell脚本中尝试:

        su <username> -c "my command"
        

        所以如果用户是“userA”:

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

        但是,例如,如果您是 userA,并且您想以 root 身份运行该部分脚本,则会提示您通过。

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

        您也可以通过首先使用 sudo 运行脚本来解决此问题

        sudo ./myScript.sh
        

        这样,脚本将原始用户保留在脚本中,您可以使用标准变量(如 ${USERNAME}${ UID}

        取决于什么更适合你。

        【讨论】:

        • su root 在脚本中也为我打开了密码提示