防止意外的标准输入读取并锁定子进程
prevent unexpected stdin reads and lock in subprocess
一个我试图解决所有情况的简单案例。
我是 运行 执行某项任务的子进程,我不希望它请求标准输入,但在极少数情况下,我可能甚至没有想到,它可能会尝试读取。
我想防止它在那种情况下挂起。
这是一个经典的例子:
import subprocess
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"])
p.wait()
这将永远挂起。
我已经尝试添加
stdin=open(os.devnull)
等等..
如果我找到有价值的解决方案,会 post。
足以让我在父进程中收到异常 - 而不是无休止地挂在 communicate/wait 上。
更新:看来问题可能比我最初预期的还要复杂,子进程(在密码和其他情况下)从其他文件描述符读取 - 比如 /dev/tty 与 [=27 交互=].可能没有我想的那么容易解决..
显然罪魁祸首是直接使用 /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()
因为 communicate() 在发送所有提供的输入后关闭标准输入文件。
看起来简单大方。
或者:
import subprocess
import os
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"],
stdin=open(os.devnull), preexec_fn=os.setsid)
p.communicate()
如果您的子进程可能要求输入密码,那么如果 tty 可用,它可能会在标准 input/output/error 流之外执行此操作,请参阅 Q: Why not just use a pipe (popen())?
中的第一个原因
与 一样,创建新会话会阻止子进程使用父进程的 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, ..)
返回的对象,check_call()
是绝对安全的。重定向发生在子进程执行之前([=27=之后],exec()
之前)——这里没有理由使用.communicate()
而不是check_call()
。
一个我试图解决所有情况的简单案例。 我是 运行 执行某项任务的子进程,我不希望它请求标准输入,但在极少数情况下,我可能甚至没有想到,它可能会尝试读取。 我想防止它在那种情况下挂起。
这是一个经典的例子:
import subprocess
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"])
p.wait()
这将永远挂起。 我已经尝试添加
stdin=open(os.devnull)
等等..
如果我找到有价值的解决方案,会 post。 足以让我在父进程中收到异常 - 而不是无休止地挂在 communicate/wait 上。
更新:看来问题可能比我最初预期的还要复杂,子进程(在密码和其他情况下)从其他文件描述符读取 - 比如 /dev/tty 与 [=27 交互=].可能没有我想的那么容易解决..
显然罪魁祸首是直接使用 /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()
因为 communicate() 在发送所有提供的输入后关闭标准输入文件。
看起来简单大方。
或者:
import subprocess
import os
p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"],
stdin=open(os.devnull), preexec_fn=os.setsid)
p.communicate()
如果您的子进程可能要求输入密码,那么如果 tty 可用,它可能会在标准 input/output/error 流之外执行此操作,请参阅 Q: Why not just use a pipe (popen())?
中的第一个原因与 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, ..)
返回的对象,check_call()
是绝对安全的。重定向发生在子进程执行之前([=27=之后],exec()
之前)——这里没有理由使用.communicate()
而不是check_call()
。