我试图解决所有情况的一个简单案例。 我正在运行一个子进程来执行某项任务,我不希望它请求标准输入,但在极少数情况下,我什至没有预料到,它可能会尝试读取。 我想防止它在那种情况下挂起。
这是一个经典的例子:
import subprocess
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"])
p.wait()
这将永远挂起。 我已经尝试添加
stdin=open(os.devnull)
等等..
如果我找到有值(value)的解决方案,将会发布。 足以让我在父进程中收到异常 - 而不是无休止地卡在通信/等待上。
更新:看来问题可能比我最初预期的还要复杂,子进程(在密码和其他情况下)从其他文件描述符读取 - 例如/dev/tty 以与 shell 交互。可能不像我想的那么容易解决..
最佳答案
如果您的子进程可能要求输入密码,那么如果 tty 可用,它可能会在标准输入/输出/错误流之外执行此操作,参见 Q: Why not just use a pipe (popen())? 中的第一个原因。
作为you've noticed ,创建一个新 session 会阻止子进程使用父进程的 tty 例如,如果你有 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
注意:除非使用 subprocess.PIPE
,否则不需要 .communicate()
。如果您使用带有真实文件描述符 (.fileno()
) 的对象,例如 open(os.devnull, . .)
。重定向发生在子进程执行之前(fork()
之后,exec()
之前)——没有理由使用.communicate()
而不是 check_call()
。
关于python - 防止意外的标准输入读取并锁定子进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33277452/