防止意外的stdin读取并在subprocess中locking

一个简单的例子,我试图解决所有情况。 我正在运行一个subprocess来执行某个任务,我不希望它要求stdin,但是在极less数情况下,我可能没有想到,它可能会尝试读取。 我想阻止它挂在那种情况下。

这里是一个典型的例子:

import subprocess p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"]) p.wait() 

这将永远挂起。 我已经尝试添加

 stdin=open(os.devnull) 

这样..

如果我find一个有价值的解决scheme将发布 对于我来说在父进程中接受一个例外就足够了 – 而不是无休止地等待沟通/等待。

更新:似乎问题可能比我最初期望的更复杂,subprocess(在密码和其他情况下)从其他文件描述符中读取 – 例如与/ dev / tty进行交互的shell。 可能不像我想的那样容易解决..

显然罪魁祸首是直接使用/ dev / tty等。

至少在Linux上,一个解决方案是添加到Popen调用下面的参数:

 preexec_fn=os.setsid 

这会导致新的会话ID被设置,并且不允许直接从tty读取。 我可能会使用下面的代码(stdin关闭以防万一):

 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() 

因为沟通()在发送所有提供的输入后关闭stdin文件。

看起来简单而优雅。

或者:

 import subprocess import os p = subprocess.Popen(["unzip", "-tqq", "encrypted.zip"], stdin=open(os.devnull), preexec_fn=os.setsid) p.communicate() 

如果您的子进程可能要求输入密码,那么可以在标准输入/输出/错误流之外执行,如果有可用的tty,请参阅Q中的第一个原因:为什么不使用管道(popen())?

正如你已经注意到的 ,创建一个新的会话阻止子ask-password.py使用父母的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调用,这样它就不会挂起等待密码,你可以使用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

为了模拟Python 2中的DEVNULL=open(os.devnull, 'r+b', 0) ,你可以使用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()否则不需要.communicate() 。 如果使用具有真实文件描述符( .fileno() )的对象.fileno()例如open(os.devnull, ..)返回,则check_call()是完全安全的。 重定向发生在子进程执行之前(在fork() ,在exec()之前) – 没有理由在这里使用check_call()而不是check_call()