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

2024-12-12 08:41:00
admin
原创
176
摘要:问题描述:我正在尝试编写一个 Python 脚本来启动一个子进程,并写入子进程的标准输入。我还希望能够确定子进程崩溃时要采取的操作。我尝试启动的进程是一个名为的程序nuke,它有自己的内置 Python 版本,我希望能够向其提交命令,然后告诉它在命令执行后退出。到目前为止,我已经弄清楚如果我在命令提示符下启动...

问题描述:

我正在尝试编写一个 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:

subprocess3.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_outputTrue,我们可以提供一个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
'
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用