如何在Python中切换至非root用户并执行命令?
我来帮你梳理下你遇到的问题,然后给出可行的解决方案:
你的尝试中的问题分析
第一个方案(
sudo su - apache && touch ~/testfile.txt)挂起:su - apache会启动一个交互式shell,但paramiko的exec_command默认是非交互式环境,这个shell会一直等待输入,导致终端挂起,永远不会执行后续的touch命令。第二个方案(
sudo su - apache -c 'touch testfile.txt')无反应:
一方面su -会切换用户的完整环境,~指向apache的家目录,如果该目录不存在或apache无写入权限,命令会静默失败;另一方面你可能没捕获错误输出,stderr里大概率有你没看到的报错信息。sudo -u apache; whoami变体无输出:
分号是shell的命令分隔符,sudo -u apache只是切换用户身份但未执行任何命令,后面的whoami还是以原用户身份运行的。正确写法应该是sudo -u apache whoami,把目标命令和sudo参数绑定在一起。subprocess调用ssh的错误:
你用subprocess.Popen(["ssh", HOST, COMMAND], shell=True)时,shell=True会把整个参数列表当作单一字符串传递给shell,导致ssh的参数解析混乱。去掉shell=True,或者把所有参数拼成符合ssh语法的字符串才能正常运行。
正确的解决方案
方案1:用Paramiko直接执行sudo -u命令
不需要通过su切换用户,直接用sudo -u apache执行目标命令,这是更适合非交互式场景的写法:
import paramiko emailList = ["your_email@example.com"] # 你的邮箱列表 privatekeyfile = '/home/myuser/.ssh/id_rsa2' username = "myuser" hostname = '123.456.11.11' # 建议指定完整路径,避免环境变量差异导致的路径问题 command = "sudo -u apache touch /home/apache/testfile.txt" for email in emailList: client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) mykey = paramiko.RSAKey.from_private_key_file(privatekeyfile) try: client.connect(hostname=hostname, username=username, pkey=mykey) stdin, stdout, stderr = client.exec_command(command) # 必须同时读取stdout和stderr,才能看到完整执行结果 output = stdout.read().decode() error = stderr.read().decode() if output: print("命令输出:", output) if error: print("错误信息:", error) finally: client.close()
注意:要确保myuser在sudoers配置中拥有免密执行该命令的权限,否则需要在exec_command时通过stdin输入密码(不推荐,建议直接配置sudo免密规则)。
方案2:用subprocess正确调用ssh
如果想用subprocess实现,要注意参数的正确传递,尽量避免shell=True:
import subprocess import sys HOST = "myuser@123.456.11.11" COMMAND = "sudo -u apache touch /home/apache/testfile.txt" private_key_path = "/home/myuser/.ssh/id_rsa2" # 按ssh的参数格式构建列表,避免解析错误 ssh_cmd = ["ssh", "-i", private_key_path, HOST, COMMAND] ssh_process = subprocess.Popen( ssh_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout, stderr = ssh_process.communicate() if stdout: print("命令输出:", stdout) if stderr: print("错误信息:", stderr, file=sys.stderr)
关键注意点
- 优先用
sudo -u username command替代su,前者是专为非交互式场景设计的切换用户执行命令的方式。 - 执行文件操作时尽量指定完整路径,避免因用户环境变量不同导致的路径找不到问题。
- 一定要捕获并打印
stderr,很多“无反应”的情况都是因为错误输出没被查看。 - 确保执行sudo的用户拥有对应权限,建议在sudoers中配置
myuser ALL=(apache) NOPASSWD: ALL(或更精确的命令范围),避免需要输入密码。
内容的提问来源于stack exchange,提问作者J. Doe

