如何写入 Python 子进程的标准输入?
- 2024-12-12 08:41:00
- admin 原创
- 176
问题描述:
我正在尝试编写一个 Python 脚本来启动一个子进程,并写入子进程的标准输入。我还希望能够确定子进程崩溃时要采取的操作。
我尝试启动的进程是一个名为的程序nuke
,它有自己的内置 Python 版本,我希望能够向其提交命令,然后告诉它在命令执行后退出。到目前为止,我已经弄清楚如果我在命令提示符下启动 Python,然后nuke
作为子进程启动,那么我就可以输入命令nuke
,但我希望能够将所有这些都放在脚本中,以便主 Python 程序可以启动nuke
,然后写入其标准输入(从而写入其内置版本的 Python)并告诉它做一些时髦的事情,所以我编写了一个像这样启动的脚本nuke
:
subprocess.call(["C:/Program Files/Nuke6.3v5/Nuke6.3", "-t", "E:/NukeTest/test.nk"])
然后什么也不会发生,因为nuke
正在等待用户输入。我现在该如何写入标准输入?
我这样做是因为我正在运行一个插件,nuke
该插件会导致它在渲染多个帧时间歇性崩溃。所以我希望这个脚本能够启动nuke
,告诉它做某事,然后如果它崩溃了,再试一次。所以如果有一种方法可以捕获崩溃并且仍然可以正常工作,那就太好了。
解决方案 1:
可能更好的方法是使用communicate
:
from subprocess import Popen, PIPE, STDOUT
p = Popen(['myapp'], stdout=PIPE, stdin=PIPE, stderr=PIPE, text=True)
stdout_data = p.communicate(input='data_to_write')[0]
“更好”,因为有这个警告:
使用communication()而不是.stdin.write、.stdout.read或.stderr.read来避免由于任何其他OS管道缓冲区填满并阻塞子进程而导致的死锁。
解决方案 2:
从subprocess
3.5 开始,有一个subprocess.run()
函数,它提供了一种初始化和与Popen()
对象交互的便捷方法。run()
它接受一个可选input
参数,您可以通过该参数将东西传递给它stdin
(就像使用一样Popen.communicate()
,但一次性传递所有内容)。
调整jro的示例以供使用run()
如下:
import subprocess
p = subprocess.run(['myapp'], input='data_to_write', capture_output=True, text=True)
执行后,p
将是一个CompletedProcess
对象。通过设置capture_output
为True
,我们可以提供一个p.stdout
属性,如果我们关心它,该属性可以让我们访问输出。text=True
告诉它使用常规字符串而不是字节。如果您愿意,您还可以添加参数,check=True
使其在退出状态(无论通过 都可以访问p.returncode
)不是 0 时抛出错误。
这是实现此目的的“现代”/快速且简单的方法。
解决方案 3:
澄清一些观点:
正如jro所提到的,正确的方法是使用subprocess.communicate
。
然而,当使用 时stdin
,您需要根据subprocess.communicate
文档启动子流程。input
`stdin=subprocess.PIPE`
请注意,如果您想要将数据发送到进程的标准输入,则需要使用 stdin=PIPE 创建 Popen 对象。同样,要获取结果元组中除 None 之外的任何内容,您还需要提供 stdout=PIPE 和/或 stderr=PIPE。
此外,qed在评论中提到,对于 Python 3.4,您需要对字符串进行编码,这意味着您需要将字节传递给 而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
me' | grep -i catch | wc -m。这通常应该返回“CATCH”中的数字字符加上换行符,结果为 6。此处 echo 的目的是将数据提供
CATCH
me给 grep。因此,我们可以将数据与 Python 子进程链中的 stdin 一起作为变量提供给 grep,然后将 stdout 作为 PIPE 传递给进程
wc`的 stdin(同时,删除多余的换行符):
import subprocess
what_to_catch = 'catch'
what_to_feed = 'CATCH
me'
# 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 '
' 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 '
', so we strip that out
output = p2.communicate(input=p1_out)[0].decode().strip()
这与此处的响应有些不同,在这里您直接通过管道传输两个进程,而无需在 Python 中直接添加数据。
希望能够帮助到别人。
解决方案 4:
可以动态地将数据写入子进程对象,而不是事先收集字符串中的所有输入以通过communicate()方法。
此示例将动物名称列表发送给 Unix 实用程序排序,并将输出发送至标准输出。
import sys, subprocess
p = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sys.stdout)
for v in ('dog','cat','mouse','cow','mule','chicken','bear','robin'):
p.stdin.write( v.encode() + b'
' )
p.communicate()
请注意,写入进程是通过 p.stdin.write(v.encode()) 完成的。我尝试使用print(v.encode(), file=p.stdin)
,但失败并显示消息TypeError: a bytes-like object is required, not 'str'
。我还没弄清楚如何让 print() 处理这个问题。
解决方案 5:
虽然.communicate()
这是推荐的方法,但它并不能解决所有用例。对于那些想要编写行流并从子进程中读取其转换的人来说,这里有一个示例代码。
import sys
from pathlib import Path
import itertools
import subprocess
import threading
def copy_to_stdin(proc, src_file: Path, mt_file: Path):
"""Example task: Write data to subproc stdin.
Note: run this on another thread to avoid deadlocks
This function reads two parallel files (src_file and mt_file), and write them as TSV record to the stdin of the sub process.
:param proc: subprocess object to write to
:param src_file: path to source file
:param mt_file: path to MT file
"""
with src_file.open() as src_lines, mt_file.open() as mt_lines:
for src_line, mt_line in itertools.zip_longest(src_lines, mt_lines):
if src_line is None or mt_line is None:
log.error(f'Input files have different number of lines')
raise ValueError('Input files have different number of lines')
line = src_line.rstrip('
') + ' ' + mt_line.rstrip('
') + '
'
proc.stdin.write(line)
proc.stdin.flush()
proc.stdin.close() # close stdin to signal end of input
cmd_line = ['yourcmd', 'arg1'] # fill your args
src_file, mt_file = ... # your files
proc = subprocess.Popen(cmd_line, shell=False,
stdout=subprocess.PIPE, stdin=subprocess.PIPE,
stderr=sys.stderr, text=True, encoding='utf8', errors='replace')
try:
copy_thread = threading.Thread(target=copy_to_stdin, args=(proc, src_file, mt_file))
copy_thread.start()
# demonstration of reading data from stdout.
for line in proc.stdout:
line = line.rstrip()
print(line)
copy_thread.join()
returncode = proc.wait()
if returncode != 0:
raise RuntimeError(f'Process exited with code {returncode}')
finally:
if proc.returncode is None:
log.warning(f'Killing process {proc.pid}')
proc.kill()
解决方案 6:
TL;DR: 关于 Python 3.10
将 '-u' 添加到 subprocess.Popen,在论坛中或其他地方找到它,
在编码并通过 stdin.write() 发送之前将 '\n' 添加到任何字符串中
我试图打开一个单独的 python 文件,并让该文件的内容显示在 tkinter 文本框中。
我想在许多程序中使用 GUI,因此它会在子进程中打开程序。通过搜索许多不同的论坛,我已经让它工作了,我将我的答案放在这里,希望能对别人有所帮助。
我的 GUI 启动了一个调用子进程的线程。
def select_program_thread(self):
self.select_program_thrd = Thread(target=self.select_program, daemon=True)
self.select_program_thrd.start()
self.master.update()
def select_program(self):
program_select = filedialog.askopenfilename(defaultextension='.py', initialdir='path oyourdir')
self.program = subprocess.Popen(['python','-u',program_select], # needed the '-u' to allow constant printing to the text box, found in a comment on a similar post, but don't remember which thread or person
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE)
while self.program.poll() == None: # check whether process is still running
try:
line = self.program.stdout.readline()
if line:
msg = line.decode() # incoming is bytes, need to decode
self.print_to_text(msg.strip())
self.program.stdout.flush() # unsure if needed but overall was working so left it alone
except Exception as e:
print(e)
self.program.wait()
然后,当我的程序需要来自控制台的任何输入时,我有一个按钮可以抓取字符串并将其转换为字节
def console_input(self):
try:
txt = (self.console_text_input.get() + '
') # Needed to add the '
' to actually send it through. got stuck otherwise
self.console_text_input.delete(0, END) # clear entry widget
msg = txt.encode() # change to bytes
self.program.stdin.write(msg) # write to subprocess
self.program.stdin.flush() # clear stdin
except Exception as e:
print(e)
测试文件
from time import sleep
print('I started Successfully')
x=3
while x>0:
print(x)
sleep(1)
x-=1
print('Please send a command to the console')
v = input()
print(f'you typed {v}')
sleep(1)
print('Finished, Program Over')
sleep(1) # needed for stdout to be read
我在程序结束时需要至少一秒钟来读取标准输出并将其放入文本框中,否则该过程会在捕获之前终止。
解决方案 7:
stdin
您可以为的参数提供一个类似文件的对象subprocess.call()
。
该对象的文档Popen
适用于此处。
要捕获输出,您应该使用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
'