如何写入 Python 子进程的标准输入?

Posted

技术标签:

【中文标题】如何写入 Python 子进程的标准输入?【英文标题】:How do I write to a Python subprocess' stdin? 【发布时间】:2012-01-18 12:06:27 【问题描述】:

我正在尝试编写一个启动子进程并写入子进程标准输入的 Python 脚本。我还希望能够确定子进程崩溃时要采取的措施。

我正在尝试启动的进程是一个名为nuke 的程序,它有自己的内置 Python 版本,我希望能够向它提交命令,然后告诉它在命令后退出执行。到目前为止,我已经发现,如果我在命令提示符下启动 Python,然后将 nuke 作为子进程启动,那么我可以向 nuke 输入命令,但我希望能够将这一切都放入一个脚本,以便主 Python 程序可以启动nuke,然后写入它的standard input(从而写入它的内置 Python 版本)并告诉它做一些时髦的事情,所以我编写了一个以 @987654327 开头的脚本@像这样:

subprocess.call(["C:/Program Files/Nuke6.3v5/Nuke6.3", "-t", "E:/NukeTest/test.nk"])

然后什么都没有发生,因为nuke 正在等待用户输入。我现在如何写入标准输入?

我这样做是因为我正在运行一个带有 nuke 的插件,这会导致它在渲染多个帧时间歇性地崩溃。所以我希望这个脚本能够启动nuke,告诉它做某事,然后如果它崩溃,再试一次。因此,如果有一种方法可以捕捉崩溃并且仍然可以,那就太好了。

【问题讨论】:

如果您希望快速将字符串写入子进程标准输入,请使用input of subprocess.run;例如,subprocess.run(['cat'], input='foobar'.encode('utf-8')) 【参考方案1】:

使用communicate 可能会更好:

from subprocess import Popen, PIPE, STDOUT
p = Popen(['myapp'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
stdout_data = p.communicate(input='data_to_write')[0]

“更好”,因为这个警告:

使用communicate() 而不是.stdin.write、.stdout.read 或.stderr.read 以避免由于任何其他操作系统管道缓冲区填满并阻塞子进程而导致的死锁。

【讨论】:

啊,太好了,谢谢,如果我只是做一个导入子流程,我还需要导入popen PIPE等吗? 不,你没有,但是你需要像subprocess.PIPE一样引用它们。此方法还导入子流程模块中的所有内容。 from subprocess import PIPE 引入到当前命名空间中,因此您可以只使用PIPE communicate 方法读取数据,直到收到 EOF。如果您想与进程动态交互,请使用p.stdin.write('data') 访问管道。有关阅读,请参阅我之前的评论。警告是关于这种通信方式的,所以请注意不要填满缓冲区。验证这一点的最简单方法就是尝试一下...... 对于 python 3.4 你需要做p.communicate(input="data for input".encode()) 当您搜索上述问题的答案时,例如,如果您需要执行“ping”命令而communicate 不支持,则此答案完全没有帮助。【参考方案2】:

澄清几点:

由于jro有mentioned,正确的做法是使用subprocess.communicate

然而,当使用subprocess.communicateinput 提供stdin 时,您需要根据docs 使用stdin=subprocess.PIPE 启动子进程。

请注意,如果您想将数据发送到进程的标准输入,您需要使用标准输入=PIPE 创建 Popen 对象。同样,要在结果元组中获取 None 以外的任何内容,您也需要提供 stdout=PIPE 和/或 stderr=PIPE。

qed 在 cmets 中还提到,对于 Python 3.4,您需要对字符串进行编码,这意味着您需要将 Bytes 传递给 input 而不是 string。这并不完全正确。根据文档,如果流以文本模式打开,则输入应该是一个字符串(源是同一页)。

如果流以文本模式打开,则输入必须是字符串。否则,它必须是字节。

所以,如果流没有在文本模式下显式打开,那么下面的内容应该可以工作:

import subprocess
command = ['myapp', '--arg1', 'value_for_arg1']
p = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = p.communicate(input='some data'.encode())[0]

我特意将上面的stderr 值保留为STDOUT 作为示例。

话虽如此,有时您可能想要另一个进程的输出,而不是从头开始构建它。假设您要运行 echo -n 'CATCH\nme' | grep -i catch | wc -m 的等价物。这通常应该返回 'CATCH' 中的数字字符加上一个换行符,结果为 6。这里的回显点是将CATCH\nme 数据提供给 grep。因此,我们可以将 Python 子进程链中的标准输入作为变量提供给 grep,然后将标准输出作为 PIPE 传递给wc 进程的标准输入(同时,去掉多余的换行符):

import subprocess

what_to_catch = 'catch'
what_to_feed = 'CATCH\nme'

# We create the first subprocess, note that we need stdin=PIPE and stdout=PIPE
p1 = subprocess.Popen(['grep', '-i', what_to_catch], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

# We immediately run the first subprocess and get the result
# Note that we encode the data, otherwise we'd get a TypeError
p1_out = p1.communicate(input=what_to_feed.encode())[0]

# Well the result includes an '\n' at the end, 
# if we want to get rid of it in a VERY hacky way
p1_out = p1_out.decode().strip().encode()

# We create the second subprocess, note that we need stdin=PIPE
p2 = subprocess.Popen(['wc', '-m'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

# We run the second subprocess feeding it with the first subprocess' output.
# We decode the output to convert to a string
# We still have a '\n', so we strip that out
output = p2.communicate(input=p1_out)[0].decode().strip()

这与here 的响应有些不同,后者直接通过管道传输两个进程,而无需直接在 Python 中添加数据。

希望对某人有所帮助。

【讨论】:

【参考方案3】:

您可以为subprocess.call()stdin 参数提供一个类似文件的对象。

Popen 对象的documentation 在这里适用。

要捕获输出,您应该改用subprocess.check_output(),它采用类似的参数。来自文档:

>>> subprocess.check_output(
...     "ls non_existent_file; exit 0",
...     stderr=subprocess.STDOUT,
...     shell=True)
'ls: non_existent_file: No such file or directory\n'

【讨论】:

以上是关于如何写入 Python 子进程的标准输入?的主要内容,如果未能解决你的问题,请参考以下文章

如何将数据写入 Python shell 管道中第一个进程的标准输入?

在 Rust 中写入子进程的标准输入?

Python子进程将数据定向到标准输入

如何在 Python 中执行将数据写入标准输入的进程?

使用文件作为子进程的标准输入和标准输出

Python asyncio子进程连续写入stdin和读取stdout/stderr