从Python脚本中以超级用户身份运行命令

61

我试图在Python脚本中使用subprocess运行超级用户进程。在IPython shell中可以这样做:

proc = subprocess.Popen('sudo apach2ctl restart',
                        shell=True, stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)

代码本身没有问题,但一旦将其放入脚本中,就会出现“sudo: apach2ctl: command not found”错误。

我猜测这是由于sudo在Ubuntu上处理环境的方式导致的。(我也尝试过sudo -E apche2ctl restart和sudo env path=$PATH apache2ctl restart,但都没有成功)

所以我的问题基本上是,如果我想作为超级用户运行apache2ctl restart,并在需要时提示用户输入超级用户密码,该怎么做?我没有在脚本中存储密码的意图。

编辑:

我已经尝试将命令作为字符串传递和分解成列表传递。在Python解释器中,使用字符串可以正确地获取密码提示(仍然无法像原始问题中的Python脚本那样工作),但使用列表只会给出sudo的帮助屏幕。

编辑2:

因此,我得出的结论是,虽然Popen在shell=True下可以与某些命令一起使用,但它需要

proc = subprocess.Popen(['sudo','/usr/sbin/apache2ctl','restart'])

没有使用'shell=True',sudo无法正常工作。

谢谢!


4
配置sudo让该用户可以无需输入密码运行特定命令,但不改变其他权限。 - Harley Holcombe
根据您的需求,您可能需要使用proc.wait()wait会等待子进程运行完毕后再继续执行。 - craymichael
你会收到错误信息 sudo: apach2ctl: command not found 是因为你在服务名 apache2ctl 中打错了一个字母。 - Josh Correia
9个回答

32

尝试:

subprocess.call(['sudo', 'apach2ctl', 'restart'])

子进程需要访问真正的标准输入/输出/错误以便提示你并读取密码。如果你将它们设置为管道,则需要自己将密码输入到该管道中。

如果您未定义它们,则会捕获sys.stdout等...


4
您没有解决密码问题,如下所示已经解决。 - Gilco

23

尝试给出apache2ctl的完整路径。


我认为你已经找到了问题所在。从解释器运行脚本会继承正在运行它的交互式 shell 的 $PATH。然而,当作为脚本运行时,$PATH 是从非交互式 shell 继承的,可能不同于前者。因此,在使用 popen() 时最好始终指定完整路径。 - Ben Blank
是的 - 每当您从脚本引用二进制文件时,就不能依赖于$PATH。顺便说一下,“apch2ctl”拼错了。 ;) - ojrac

18

另一种方法是将您的用户设为无需密码的sudo用户

在命令行中输入以下内容:

sudo visudo

接下来添加以下内容,并将<username>替换为您自己的用户名:

<username> ALL=(ALL) NOPASSWD: ALL

这将允许用户在无需请求密码的情况下执行sudo命令(包括由该用户启动的应用程序)。然而,这可能会存在安全风险。


15

我在Python 3.5中使用了这个方法。我使用了subprocess模块。像这样使用密码非常不安全

subprocess模块将命令作为字符串列表,因此可以事先创建一个列表,使用split()或稍后传递整个列表。阅读文档以获取更多信息。

我们在这里所做的是回显密码,然后使用pipe通过'-S'参数将其传递给sudo。

#!/usr/bin/env python
import subprocess

sudo_password = 'mysecretpass'
command = 'apach2ctl restart'
command = command.split()

cmd1 = subprocess.Popen(['echo',sudo_password], stdout=subprocess.PIPE)
cmd2 = subprocess.Popen(['sudo','-S'] + command, stdin=cmd1.stdout, stdout=subprocess.PIPE)

output = cmd2.stdout.read().decode() 

注意 - 由于明显的原因,不要直接在代码中存储密码,而应该使用环境变量或其他更安全的方法来获取相同的结果。

1
如果需要输入密码,我不会将密码存储在代码中,而是使用类似于 getpass 的东西,使您的第一个命令为 cmd1 = subprocess.Popen(['echo',getpass.getpass('password:')], stdout=subprocess.PIPE) 或者像 Jozsef Turi 提到的那样。 - Silfheed
你不应该在代码中存储你的密码/用户名,这会导致权限升级。 - boaz tene

9

最安全的方法是事先提示输入密码,然后将其传递到命令中。提示输入密码可以避免在代码中保存密码,并且它也不会显示在bash历史记录中。以下是一个示例:

from getpass import getpass
from subprocess import Popen, PIPE

password = getpass("Please enter your password: ")
# sudo requires the flag '-S' in order to take input from stdin
proc = Popen("sudo -S apach2ctl restart".split(), stdin=PIPE, stdout=PIPE, stderr=PIPE)
# Popen only accepts byte-arrays so you must encode the string
proc.communicate(password.encode())

1
我喜欢这个。不过,为了更安全地分割参数,我会使用 shlex.split() 而不是 str.split() - Szabolcs

5
您需要像这样使用Popen:
cmd = ['sudo', 'apache2ctl', 'restart']
proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

它期望一个列表。

9
当shell=True时,一个字符串和序列参数之间存在区别,请阅读http://docs.python.org/library/subprocess.html。 - jfs
啊,我明白了,我以前从没这样用过。 - Harley Holcombe
4
当你传入一个列表时,不要设置shell=True。 - आनंद

4

要以root身份运行命令,并在命令提示符处输入密码,您可以按以下方式执行:

import subprocess
from getpass import getpass

ls = "sudo -S ls -al".split()
cmd = subprocess.run(
    ls, stdout=subprocess.PIPE, input=getpass("password: "), encoding="ascii",
)
print(cmd.stdout)

以您的示例为例,可能是这样的:

import subprocess
from getpass import getpass

restart_apache = "sudo /usr/sbin/apache2ctl restart".split()
proc = subprocess.run(
    restart_apache,
    stdout=subprocess.PIPE,
    input=getpass("password: "),
    encoding="ascii",
)

3

我尝试了所有的解决方案,但都没有成功。想要使用 Celery 运行长时间运行的任务,但是需要使用 subprocess.call() 运行 sudo chown 命令。

这是对我有用的内容:

在命令行中添加安全环境变量,输入:

export MY_SUDO_PASS="user_password_here"

为了测试是否正常工作,请键入:

echo $MY_SUDO_PASS
 > user_password_here

要在系统启动时运行它,请将其添加到此文件的末尾:

nano ~/.bashrc  

#.bashrc
...
existing_content:

  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi
...

export MY_SUDO_PASS="user_password_here"

您可以在此处添加所有环境变量密码、用户名、主机等。

如果您的变量已准备好,可以运行以下命令:

更新:

echo $MY_SUDO_PASS | sudo -S apt-get update

或者安装 Midnight Commander

echo $MY_SUDO_PASS | sudo -S apt-get install mc

使用sudo启动Midnight Commander

echo $MY_SUDO_PASS | sudo -S mc

或者从Python shell(或Django / Celery)中,递归更改目录所有权:

python
>> import subprocess
>> subprocess.call('echo $MY_SUDO_PASS | sudo -S chown -R username_here /home/username_here/folder_to_change_ownership_recursivley', shell=True)

希望能帮到你。

1
不用说,将您的sudo密码存储在未加密的文件中并将其添加到您的shell环境中是绝对不安全的。 - Brandon

0

你可以使用这种方式,甚至可以捕获错误,还可以向你的命令添加变量。-

val = 'xy
response = Popen(f"(sudo {val})", stderr=PIPE, stdout=PIPE, shell=True)
output, errors = response.communicate()

希望这能有所帮助。

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