将pipe命令和sudo与Python子流程组合时出现问题

将pipe命令和sudo与Python子流程组合时出现问题,python,subprocess,Python,Subprocess,我正试图利用Python模块子流程在Mac上自动执行终端命令。具体来说,我正在运行一个在我的机器上创建的。但是,该命令需要root权限和管道: echo " rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080 " | sudo pfctl -ef - 为了通过子流程将我的根密码传递给shell命令,我按照下面的代码示例创建了一个脚本: from subprocess import PIPE, Po

我正试图利用Python模块
子流程
在Mac上自动执行终端命令。具体来说,我正在运行一个在我的机器上创建的。但是,该命令需要root权限和管道:

echo "
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
" | sudo pfctl -ef - 
为了通过
子流程
将我的根密码传递给shell命令,我按照下面的代码示例创建了一个脚本:

from subprocess import PIPE, Popen
p = Popen(['echo', '"rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080"\n'], stdin=PIPE, stderr=PIPE, universal_newlines=True)
p2 = Popen(['sudo', '-S']+['pfctl', '-ef', '-'], stdin=p.stdout, stderr=PIPE, universal_newlines=True) 
return p2.communicate('my_root_password\n')[1] 
请注意,为了实现
echo
输出到命令
pfctl-ef-
的管道,我创建了两个
Popen
对象,并将第一个对象的
stdout
传递给第二个对象的
stdin
参数,如,我使用
Popen.communicate
将root密码写入stdin

但是,上面的脚本不起作用,因为终端仍然提示我输入根密码。奇怪的是,当使用不带管道的命令时,例如运行
sudo pfctl-s nat
(显示当前端口映射设置),我能够成功地将根密码写入stdin:

上面的代码可以工作,因为映射配置显示时没有任何密码提示

如何更改我的第一个Python脚本,以便在使用
Popen.communicate
将密码写入stdin后,不会提示我输入根密码



我在macOS Sierra 10.12.5上运行此代码,我认为这只是管道未正确连接的简单情况。您没有为第一个进程的
stdout
指定管道,因此从外观上看,输出只是打印到终端,然后进程完成

当第二个进程启动时,它将提示输入密码,并且据我所知,将正确接收密码。但是,
communicate
方法随后关闭输入并等待过程完成。据我所知,第一个进程的输出永远不会到达第二个进程,这就是脚本无法工作的原因。与其创建一个单独的
echo
过程,为什么不通过
communicate
发送所需的所有文本数据呢

另一个问题(我没有MAC要检查)是
sudo
直接将提示打印到终端(即通过
/dev/tty
而不是
stdout
)。在我的
sudo
(在Debian上)版本中,添加
-S
选项会使其将提示打印到
stderr
。但是,在MAC上,
-S
选项似乎无法实现这一点。请尝试使用
-p'
禁用提示

把所有东西放在一起,这应该是可行的:

from subprocess import PIPE, Popen
from getpass import getpass

password = getpass()

cmd = ['sudo', '-k', '-S', '-p', '', 'pfctl', '-ef', '-']
p = Popen(cmd, stdin=PIPE, stderr=PIPE, universal_newlines=True) 
text = password + '\n'
text += 'rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080\n'
p.communicate(text)
安全说明
此答案已更新为不使用纯文本密码。请参阅下面的评论,以了解为什么这是一个坏主意的好例子!还要注意的是,使用Python在内存中存储密码并不是完全安全的,就像内存被交换到磁盘一样,这将包括明文形式的密码。对于较低级别的语言,
mlock
系统调用将用于防止任何包含密码的内存被交换。

不是答案,但为什么不删除sudo并以root用户身份运行脚本?@mVChr这是个好主意,但我实际上计划将其作为一个程序的一部分运行,该程序查询用户密码以进行更改,在本例中,更新端口映射规则。
sudo
stdin
读取您的密码,您的
stdin
连接到其他进程
stdout
sudo
无法读取您的密码。@这很有道理。我有没有办法解决这个问题。用户必须在sudoers文件中。或2。使用
子流程。致电
非常感谢!但是,这样做是可行的,它只在每次在新的终端窗口中运行代码时更新配置文件。你知道为什么会这样吗?我假设这只是
pfctl
的一个功能,但是,Mac端口管理器可能与此有关吗?啊,可能是因为
sudo
在第二轮不需要密码,所以密码会被发送到
pfctl
。尝试将
-k
选项添加到
sudo
,这将确保它始终要求输入密码。再次感谢!现在可以了!一旦系统允许我(在21小时内)就会奖励我。
from subprocess import PIPE, Popen
from getpass import getpass

password = getpass()

cmd = ['sudo', '-k', '-S', '-p', '', 'pfctl', '-ef', '-']
p = Popen(cmd, stdin=PIPE, stderr=PIPE, universal_newlines=True) 
text = password + '\n'
text += 'rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080\n'
p.communicate(text)