窥探Python中的Popen管道流

时间:2012-10-07 18:25:23

标签: python popen fastq

背景
Linux上的Python 2.6.6。 DNA序列分析管道的第一部分 我想从已安装的远程存储(LAN)中读取可能的gzip文件,如果它是gzip压缩的话;将它压缩到一个流(即使用gunzip FILENAME -c)并且如果流(文件)的第一个字符是“@”,则将整个流路由到一个过滤程序,该程序接受标准输入的输入,否则直接将其直接传送到本地磁盘上的文件。我想最大限度地减少从远程存储中读取/搜索的文件数量(只需一次通过文件就不可能吗?)。

示例输入文件的内容,前四行对应于FASTQ格式的一条记录:

@I328_1_FC30MD2AAXX:8:1:1719:1113/1                                        
GTTATTATTATAATTTTTTACCGCATTTATCATTTCTTCTTTATTTTCATATTGATAATAAATATATGCAATTCG
+I328_1_FC30MD2AAXX:8:1:1719:1113/1                                        
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhahhhhhhfShhhYhhQhh]hhhhffhU\UhYWc

不应通过管道传输到过滤程序中的文件包含如下所示的记录(前两行对应于FASTA格式的一条记录):

>I328_1_FC30MD2AAXX:8:1:1719:1113/1
GTTATTATTATAATTTTTTACCGCATTTATCATTTCTTCTTTATTTTCATATTGATAATAAATATATGCAATTCG

有些人编写了半伪代码来实现我想要做的事情(我知道这不可能像我写的那样)。我希望它有道理:

if gzipped:
    gunzip = Popen(["gunzip", "-c", "remotestorage/file.gz"], stdout=PIPE)
    if gunzip.stdout.peek(1) == "@": # This isn't possible
        fastq = True
    else:
        fastq = False
if fastq:
    filter = Popen(["filter", "localstorage/outputfile.fastq"], stdin=gunzip.stdout).communicate()
else:
    # Send the gunzipped stream to another file

忽略这样一个事实:代码不会像我在这里编写的那样运行,并且我没有错误处理等,所有这些都已经在我的其他代码中了。我只想帮助窥视流或找到解决方法。如果你能gunzip.stdout.peek(1)我会很棒,但我意识到这是不可能的。

到目前为止我尝试了什么:
我认为subprocess.Popen可能帮助我实现这一点,我尝试了很多不同的想法,其中包括尝试使用某种io.BufferedRandom()对象来编写流,但我无法弄清楚如何会工作。我知道流是不可搜索的,但也许解决方法可能是读取gunzip流的第一个字符,然后创建一个新的流,您首先输入“@”或“>”取决于文件内容,然后将gunzip.stdout-stream的其余部分填充到新流中。然后将这个新流输入过滤器的Popen stdin。

请注意,文件大小可能比可用内存大几倍。我不希望从远程存储执行多个源文件的单个读取,也不希望不必要的文件访问。

欢迎任何想法!请问我问题所以我可以澄清一下我是否说得不够清楚。

2 个答案:

答案 0 :(得分:1)

以下是第一个输入“@”或“>”的实现取决于文件内容,然后将gunzip.stdout-stream的其余部分填充到新流提案中。我只测试了测试的本地文件分支,但它应该足以证明这个概念。

if gzipped:
    source = Popen(["gunzip", "-c", "remotestorage/file.gz"], stdout=PIPE)
else:
    source = Popen(["cat", "remotestorage/file"], stdout=PIPE)
firstchar = source.stdout.read(1)
# "unread" the char we've just read
source = Popen([r"(printf '\x%02x' && cat)" % ord(firstchar)],
               shell=True, stdin=source.stdout, stdout=PIPE)

# Now feed the output to a filter or to a local file.
flocal = None
try:
    if firstchar == "@":
        filter = Popen(["filter", "localstorage/outputfile.fastq"],
                       stdin=source.stdout)
    else:
        flocal = open('localstorage/outputfile.stream', 'w')
        filter = Popen(["cat"], stdin=source.stdout, stdout=flocal)
    filter.communicate()
finally:
    if flocal is not None:
        flocal.close()

想法是从源命令的输出中读取单个字符,然后使用(printf '\xhh' && cat)重新创建原始输出,从而有效地实现了窥视。替换流将shell=True指定为Popen,将其留给shell并cat执行繁重的工作。数据始终保持在管道中,永远不会完全读入内存。请注意,shell的服务仅在对Popen的单个调用中请求,该调用实现了对未读取的字节的读取,而不是涉及用户提供的文件名的调用。即使在那时,字节也会转义为十六进制,以确保在调用printf时shell不会破坏它。

可以进一步清理代码以实现名为peek的实际函数,该函数返回隐藏的内容和替换new_source

答案 1 :(得分:0)

在Python中包装shell命令没有意义。您可以在Python中实现所需的一切,但不会出现问题:

  1. 打开输入文件并读取前3个字节。如果它们等于1F 8B 08那么它应该是gzip文件。
  2. 重置文件标记
  3. 如果文件内容是gzip文件或读取文件,则将文件内容传递给zlib.decompress()
  4. 如果需要,转到过滤功能
  5. 将结果写入文件
  6. 修改

    这不起作用,因为在传递给zlib之前需要剥离gzip头。但是,有可能检查前3个字节,执行fh.seek(0)并将文件传递给gzip.open(),如果你想确定文件是gzip(使用DEFLATE压缩)。

    将文件传递给gzip可能更容易,并且如果文件没有被压缩,则捕获抛出的异常:

    import gzip
    
    try:
        in_file = gzip.open("infile")
        f_contents = in_file.read()
    except IOError, e:
        # Re-raise exception if exception message is not "Not a gzipped file"
        # Perhaps it would be safer to check the header!
        if e.__str__() != "Not a gzipped file":
            raise
        in_file = open("infile")
        f_contents = in_file.read()
    
    if f_contents[0] == "@":
        result = filter_function(f_contents)
    else:
        result = f_contents
    
    new_file = open("new_file", "w")
    new_file.write(result)