我正试图解决所有情况的一个简单案例。 我正在运行一个子进程来执行某个任务,我不希望它要求stdin,但在极少数情况下,我甚至可能没想到,它可能会尝试阅读。 我想在这种情况下防止它挂起。
这是一个典型的例子:
import subprocess
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"])
p.wait()
这将永远挂起。 我已经尝试添加
了stdin=open(os.devnull)
等......
如果找到有价值的解决方案,会发布。 对于我来说,在父进程中接收异常就足够了 - 而不是无休止地继续沟通/等待。
更新:似乎问题可能比我最初的预期更复杂,子进程(在密码和其他情况下)从其他文件描述符读取 - 比如/ dev / tty与shell交互。可能不像我想的那么容易解决..
答案 0 :(得分:3)
如果你的子进程可能要求输入密码,那么如果tty可用,它可以在标准输入/输出/错误流之外进行,请参阅Q: Why not just use a pipe (popen())?
中的第一个原因作为you've noticed,创建新会话会阻止子进程使用父进程,例如,如果您有ask-password.py
脚本:
#!/usr/bin/env python
"""Ask for password. It defaults to working with a terminal directly."""
from getpass import getpass
try:
_ = getpass()
except EOFError:
pass # ignore
else:
assert 0
然后将其作为子进程调用,以便它不会挂起等待密码,您可以使用start_new_session=True
参数:
#!/usr/bin/env python3
import subprocess
import sys
subprocess.check_call([sys.executable, 'ask-password.py'],
stdin=subprocess.DEVNULL, start_new_session=True,
stderr=subprocess.DEVNULL)
stderr也被重定向到此处,因为getpass()
将其用作后备,以打印警告和提示。
要在Python 2上的Unix上模拟start_new_session=True
,您可以使用preexec_fn=os.setsid
。
To emulate subprocess.DEVNULL
on Python 2, you could use DEVNULL=open(os.devnull, 'r+b', 0)
或通过stdin=PIPE
并使用.communicate()
立即关闭它:
#!/usr/bin/env python2
import os
import sys
from subprocess import Popen, PIPE
Popen([sys.executable, 'ask-password.py'],
stdin=PIPE, preexec_fn=os.setsid,
stderr=PIPE).communicate() #NOTE: assume small output on stderr
注意:除非您使用.communicate()
,否则您不需要subprocess.PIPE
。如果您使用具有真实文件描述符(check_call()
)的对象(.fileno()
返回),则open(os.devnull, ..)
非常安全。重定向发生在子进程执行之前(fork()
之后,exec()
之前) - 没有理由在此使用.communicate()
而不是check_call()
。
答案 1 :(得分:1)
显然,罪魁祸首是直接使用/ dev / tty等。
至少在Linux上,一个解决方案是向Popen调用以下参数:
preexec_fn=os.setsid
会导致设置新的会话ID,并且不允许直接从tty读取。我可能会使用以下代码(stdin close就是以防万一):
import subprocess
import os
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"],
stdin=subprocess.PIPE, preexec_fn=os.setsid)
p.stdin.close() #just in case
p.wait()
最后两行可以被一次调用替换:
p.communicate()
因为在发送所有提供的输入后,communic()关闭了stdin文件。
看起来简单而优雅。
可替换地:
import subprocess
import os
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"],
stdin=open(os.devnull), preexec_fn=os.setsid)
p.communicate()