如何使用su命令以特定用户身份执行bash脚本的其余部分?

182

我编写了一个脚本,接受一个字符串作为参数,该字符串是用户名和项目的连接。该脚本应该在用户之间切换(su),根据项目字符串转到特定目录。

我想要做的基本上是:

su $USERNAME;  
cd /home/$USERNAME/$PROJECT;  
svn update;  

问题是,一旦我执行了su...命令,它就会停在那里。这很有道理,因为执行的流程已经转移到切换到该用户上。一旦我退出,然后其他事情才执行,但它不按预期工作。

我在svn命令之前加了su,但命令失败了(即它没有在所需目录中更新svn)。

如何编写一个脚本,允许用户切换用户并调用svn(以及其他一些东西)?

9个回答

171
更简单的方法是:使用sudo运行shell并使用heredoc提供命令。
#!/usr/bin/env bash
whoami
sudo -i -u someuser bash << EOF
echo "In"
whoami
EOF
echo "Out"
whoami

(此答案原始链接在SuperUser上发布)


7
这个答案效果最好。我建议使用选项“-i”来获得“someuser”的预期环境。 - not2savvy
3
如果没有像AIX那样默认安装的话,sudo可能很方便,但它并不好用。使用su -c 'commands'才是正确的方法。 - Tricky
3
史诗级解决方案! - 3bdalla
如何使变量在herdoc的范围内可用? - dotslashlu
1
@DJ_Stuffy_K:没关系。你只需要输入当前登录用户的密码,这需要 root 权限。请参阅 sudo 和 su 之间的区别 - Dan Dascalescu
显示剩余4条评论

108
是使用"sudo"命令而不是"su"

您可能需要添加此

username1 ALL=(username2) NOPASSWD: /path/to/svn

将以下内容添加到/etc/sudoers文件中:

并将您的脚本更改为:

sudo -u username2 -H sh -c "cd /home/$USERNAME/$PROJECT; svn update" 

将username2替换为您想要运行SVN命令的用户,而username1是运行脚本的用户。

如果需要多个用户运行此脚本,请使用%groupname而不是username1。


我有一个类似的问题,但我想为其他用户运行chsh。我的问题在这里列出:https://dev59.com/Fm_Xa4cB1Zd3GeqP02xp。我如何在我的情况下适应你的答案? - Kim Stacks
我已经这样做了,但它仍然要求我输入密码。 - Hippyjim
@Hippyjim你确定你把用户名搞对了吗? - Kimvais
1
我做了 - 结果发现我还需要允许使用/bin/bash。 - Hippyjim
4
无论你使用 sudo 还是 su 都不那么重要,但是 sudo 更加安全和方便。 - tripleee
“sudo”可能很方便,但如果它不是开箱即用的(例如在AIX上),那就没什么用了。正确的方法是使用“su -c 'commands'”。 - Tricky

70

你需要将所有不同用户的命令作为它们自己的脚本来执行。如果只有一个或几个命令,则内联应该可以工作。如果有很多命令,则最好将它们移动到自己的文件中。

su -c "cd /home/$USERNAME/$PROJECT ; svn update" -m "$USERNAME" 

7
这是唯一正确的答案。对于这个操作,不需要使用sudo。 - helvete
1
您可能还需要向 shell 提供 su -s /bin/bash - mixel
最佳的解决方案适用于root用户。 - rfinz
1
对于那些默认未安装sudo的用户(我在说你,AIX),最佳答案是什么? - Tricky
对于那些不想使用“sudo”或将自己的用户添加到sudoers文件的Debian用户,这是一个很好的答案。 - iago

66

这里是另一种方法,对我来说更加方便(我只想放弃 root 权限,并让我的脚本从受限用户运行):您可以使脚本以正确的用户身份重新启动自己。这种方法比使用 sudosu -c 的 "嵌套脚本" 更易读。 假设它最初以 root 权限启动。然后代码将如下所示:

#!/bin/bash
if [ $UID -eq 0 ]; then
  user=$1
  dir=$2
  shift 2     # if you need some other parameters
  cd "$dir"
  exec su "$user" "$0" -- "$@"
  # nothing will be executed from root beyond that line,
  # because exec replaces running process with the new one
fi

echo "This will be run from user $UID"
...

7
我想知道为什么这个没有更高的评分。它在保持所有内容都使用 bash 的同时,解决了最初的问题。 - SirVer
2
或者按照su(1)中所述的方式运行runuser -u $user -- "$@" - cghislai
1
exec su "$user" "$0" -- "$@" - macieksk
1
谢谢@macieksk,发现得好。我会更新的。--真的很有用。 - MarSoft
2
将exec行替换为exec sudo -i -u“$user”$(readlink -f“$0”)--“$@”对我有效。 - darkdragon
显示剩余4条评论

59

使用以下脚本之类的脚本来让另一个用户执行剩余或部分脚本:

#!/bin/sh

id

exec sudo -u transmission /bin/sh - << eof

id

eof

11
您可能希望使用“sudo -i -u ...”以确保像$ HOME这样的内容被正确设置。 - Bryan Larsen
2
抱歉问一个新手问题,这里的id是什么意思? - Nitin Jadhav
2
@NitinJadhav在这里只是为了显示当前用户的ID而使用它,root的ID是0,所以第一个ID将显示一些数字,但第二个ID肯定会显示0(因为第二个ID是由root运行的块内执行的)。您可以使用whoami代替id,它将返回名称而不是ID。 - Mohammed Noureldin
@MohammedNoureldin 谢谢! - Nitin Jadhav
“sudo”可能很方便,但如果它不是开箱即用的(例如在AIX上),那就没什么用了。正确的方法是使用“su -c 'commands'”。 - Tricky

8

请使用sudo命令代替。

编辑:正如Douglas指出的那样,你不能在sudo中使用cd命令,因为它不是一个外部命令。你需要在子shell中运行命令才能使cd命令生效。

sudo -u $USERNAME -H sh -c "cd ~/$PROJECT; svn update"

sudo -u $USERNAME -H cd ~/$PROJECT
sudo -u $USERNAME svn update

您可能会被要求输入该用户的密码,但只需一次。

这样做是行不通的 - 在第一个sudo执行完成后,cd命令将会失效。 - Douglas Leeder
实际上,您甚至无法直接调用“cd”命令,因为它不是一个“外部”命令。 - iamamac
“sudo”可能很方便,但如果它不是开箱即用的(例如在AIX上),那就没什么用了。正确的方法是使用“su -c 'commands'”。 - Tricky

7

在shell脚本中无法更改用户。其他答案中描述的使用sudo的解决方法可能是您最好的选择。

如果您足够疯狂,要以root身份运行perl脚本,可以使用$< $( $> $)变量来保持实际/有效的uid/gid,例如:

#!/usr/bin/perl -w
$user = shift;
if (!$<) {
    $> = getpwnam $user;
    $) = getgrnam $user;
} else {
    die 'must be root to change uid';
}
system('whoami');

-1 因为使用 sudo 命令可以暂时获取其他用户的权限。 - Kimvais
5
从字面上讲,这是不可能的,因为无法更改作为 shell 脚本本身运行的用户(这就是最初的问题所问的)。使用 sudo 调用其他进程并不会更改脚本本身正在运行的用户。 - P-Nuts

2

这对我很有帮助

我将我的“配置”与我的“启动”分开了。

 # Configure everything else ready to run 
  config.vm.provision :shell, path: "provision.sh"
  config.vm.provision :shell, path: "start_env.sh", run: "always"

然后在我的start_env.sh文件中

#!/usr/bin/env bash

echo "Starting Server Env"
#java -jar /usr/lib/node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.40.0.jar  &
#(cd /vagrant_projects/myproj && sudo -u vagrant -H sh -c "nohup npm install 0<&- &>/dev/null &;bower install 0<&- &>/dev/null &")
cd /vagrant_projects/myproj
nohup grunt connect:server:keepalive 0<&- &>/dev/null &
nohup apimocker -c /vagrant_projects/myproj/mock_api_data/config.json 0<&- &>/dev/null &

-2
受到@MarSoft的启发,但我将行改成了以下内容:
USERNAME='desireduser'
COMMAND=$0
COMMANDARGS="$(printf " %q" "${@}")"
if [ $(whoami) != "$USERNAME" ]; then
  exec sudo -E su $USERNAME -c "/usr/bin/bash -l $COMMAND $COMMANDARGS"
  exit
fi

我使用了sudo允许无密码执行脚本。如果您想要为用户输入密码,请删除sudo。如果您不需要环境变量,请从sudo中删除-E/usr/bin/bash -l确保对已初始化的环境执行profile.d脚本。

“sudo”可能很方便,但如果它不是开箱即用的(例如在AIX上),那就没什么用了。正确的方法是使用“su -c 'commands'”。 - Tricky
@Tricky 或许应该阅读完整个答案,它已经建议删除 sudo。实际上,sudo 并不是那么简单,在很多情况下,你甚至需要 sudo -E 和 sudoers.d 中的配置条目来允许在没有 tty 的情况下执行 !requiretty。但是有很多情况下,sudo 对于自动调用脚本是必要的,这时密码对话框可能会干扰。因此,我不会从标准解决方案中删除它。 - Trendfischer
问题代码存在明显的错误——它会破坏带有空格的脚本名称或参数;请记住,在字符串中放置"$@"意味着超过第一个参数的其他参数被追加到单独的字符串中,不是包含在-c参数中。如果您想要使其安全,可以printf -v arg_q '%q ' "$0" "$@",然后使用su "$USERNAME" -c "/usr/bin/bash -l $arg_q" - Charles Duffy
@CharlesDuffy 你说得对!我为了这个答案简化了我的生产脚本 :-( 只需添加一个 COMMANDARGS=$@ 就可以解决 -c 的问题。带有空格的参数以前不是问题,但我实现了你的好输入。我只是做了一些实验使它工作。我已经编辑了问题,希望我没有粘贴另一个错误。感谢您的评论,虽然有点羞辱但是很必要。 - Trendfischer

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