使用subprocess.Popen将数据流式传输到命令中

时间:2015-09-18 23:14:32

标签: python pipe subprocess

我经常需要对包含标题的文件集合进行排序。因为排序取决于标题的内容,所以这个用例比类似的问题更复杂(例如,Is there a way to ignore header lines in a UNIX sort?)。

我希望使用Python来读取文件,输出第一个文件的标题,然后将尾部管道排序。我已经尝试过这个概念证明:

#!/usr/bin/env python

import io
import subprocess
import sys

header_printed = False

sorter = subprocess.Popen(['sort'], stdin=subprocess.PIPE)

for f in sys.argv[1:]:
    fd = io.open(f,'r')
    line = fd.readline()
    if not header_printed:
        print(line)
        header_printed = True
    sorter.communicate(line)

当调用header-sort fileA fileB时,fileA和fileB包含

之类的行
c   float   int
Y   0.557946     413
F   0.501935     852
F   0.768102     709

我明白了:

# sort file 1
Traceback (most recent call last):
  File "./archive/bin/pipetest", line 17, in <module>
    sorter.communicate(line)
  File "/usr/lib/python2.7/subprocess.py", line 785, in communicate
    self.stdin.write(input)
ValueError: I/O operation on closed file

问题是通信需要一个字符串,管道在写入后关闭。这意味着必须将内容完全读入内存。沟通不带发电机(我试过)。

更简单的证明是:

>>> import subprocess
>>> p = subprocess.Popen(['tr', 'a-z', 'A-Z'], stdin=subprocess.PIPE)
>>> p.communicate('hello')
HELLO(None, None)
>>> p.communicate('world')
Traceback (most recent call last):
  File "<ipython-input-14-d6873fd0f66a>", line 1, in <module>
    p.communicate('world')
  File "/usr/lib/python2.7/subprocess.py", line 785, in communicate
    self.stdin.write(input)
ValueError: I/O operation on closed file

所以,问题是,在Python中将数据流式传输到管道中的正确方法(使用Popen或其他方式)是什么?

3 个答案:

答案 0 :(得分:2)

对于您的具体情况,如果您仅通过subprocess.PIPE获取单个标准句柄(在您的情况下为stdin),那么在您的示例中,您可以安全地调用sorter.stdin.write(line)过度。完成输出后,调用sorter.stdin.close()使sort知道输入已完成,并且它可以执行实际的排序和输出工作(sorter.communicate()没有参数可能也会起作用;否则,在关闭stdin之后,你可能想要致电sorter.wait()让它完成。)

如果你需要处理多个管道标准句柄,正确的方法是threading,每个管道必须处理超出第一个管道的专用线程(概念相对简单,但重量级,并介绍所有头痛的线程),或使用select模块(或在Python 3.4+,selectors模块),这是非常棘手的,但可以(在某些情况下)更有效。最后,有creating temporary files for output,因此您可以在进程写入文件时直接写入进程的stdin(因此不会阻塞);然后,您可以随意读取该文件(请注意,子进程在退出之前不一定会刷新它自己的输出缓冲区,因此输出可能无法及时响应您的输入,直到进一步的输入和输出已填满并刷新缓冲液)。

subprocess.Popen .communicate()方法使用线程或select模块原语本身(取决于操作系统支持;实现在various _communicate methods here下),只要您通过{{ 1}}用于多个标准句柄;这就是你必须要做的事情。

答案 1 :(得分:1)

直接写入管道:

#!/usr/bin/env python2
import fileinput
import subprocess

process = subprocess.Popen(['sort'], stdin=subprocess.PIPE)
with process.stdin as pipe, fileinput.FileInput() as file:
    for line in file:
        if file.isfirstline(): # print header
            print line,
        else: # pipe tails
            pipe.write(line)
process.wait()

答案 2 :(得分:0)

您可以使用stdinstdout的写入/读取,但是根据您的子流程,您需要一个“刷新机制”来让子流程处理您的输入。下面的代码适用于第一部分,但由于它关闭stdin,它也会杀死子进程。如果您使用flush()更改它,或者如果您可以添加一些尾随字符来推送您的子流程,那么您可以使用它。否则,我建议您查看Multithreading in Python,尤其是pipes

p=subprocess.Popen(['tr','a-z','A-Z'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
p.stdin.write("hello\n")
p.stdin.close()
p.stdout.readline()
'HELLO\n'