如何通过 Python 子进程管道传输到文件或 shell 程序?

How do I pipe to a file or shell program via Pythons subprocess?

我正在处理一些相当大的 gzip 文本文件,我必须解压缩、编辑和重新压缩这些文件。我使用 Pythons gzip 模块进行解压缩和压缩,但我发现我当前的实现远非最佳:

input_file = gzip.open(input_file_name, 'rb')

output_file = gzip.open(output_file_name, 'wb')

for line in input_file:
    # Edit line and write to output_file

这种方法慢得令人难以忍受——可能是因为使用 gzip 模块进行每行迭代涉及巨大的开销:我最初还 运行 一个行计数例程,其中我 - 使用 gzip 模块 -读取文件块,然后计算每个块中换行符的数量,这非常快!

因此,其中一项优化绝对应该是分块读取我的文件,然后仅在解压缩这些块后才执行每行迭代。

作为额外的优化,我看到了一些通过子进程在 shell 命令中解压缩的建议。使用这种方法,上面第一行的等价物可以是:

from subprocess import Popen, PIPE

file_input = Popen(["zcat", fastq_filename], stdout=PIPE)

input_file = file_input.stdout

使用这种方法 input_file 变成一个类文件对象。我不确切知道它在可用属性和方法方面与真实文件对象有何不同,但一个区别是您显然不能使用 seek,因为它是流而不是文件。

这确实 运行 更快而且它应该 - 除非你 运行 你的脚本在单核机器上声称是。后者必须意味着 subprocess 如果可能会自动将不同的线程发送到不同的内核,但我不是这方面的专家。

现在解决我当前的问题:我想以类似的方式压缩我的输出。也就是说,我不想使用 Pythons gzip 模块,而是想将其通过管道传递给子进程,然后调用 shell gzip。这样我就有可能在不同的核心进行阅读、编辑和写作,这对我来说听起来非常有效。 我对此做了微不足道的尝试,但尝试写入 output_file 导致了一个空文件。最初,我使用 touch 命令创建一个空文件,因为如果文件不存在,Popen 将失败:

call('touch ' + output_file_name, shell=True)

output = Popen(["gzip", output_file_name], stdin=PIPE)

output_file = output.stdin

非常感谢任何帮助,顺便说一句,我正在使用 Python 2.7。谢谢

你的意思是 output_file = gzip_process.stdin。之后你可以使用 output_file 就像你之前使用过 gzip.open() 对象(不求)。

如果结果文件为空,请检查您是否在 Python 脚本末尾调用了 output_file.close()gzip_process.wait()。此外,gzip 的用法可能不正确:如果 gzip 将压缩输出写入其标准输出,则传递 stdout=gzip_output_file where gzip_output_file = open(output_file_name, 'wb', 0).

这是一个如何做到这一点的工作示例:

#!/usr/bin/env python

from subprocess import Popen, PIPE

output = ['this', 'is', 'a', 'test']

output_file_name = 'pipe_out_test.txt.gz'

gzip_output_file = open(output_file_name, 'wb', 0)

output_stream = Popen(["gzip"], stdin=PIPE, stdout=gzip_output_file)  # If gzip is supported

for line in output:
    output_stream.stdin.write(line + '\n')

output_stream.stdin.close()
output_stream.wait()

gzip_output_file.close()

如果我们的脚本只写到控制台并且我们想要压缩输出,一个 shell 等同于上面的命令可以是:

script_that_writes_to_console | gzip > output.txt.gz