背景
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。
请注意,文件大小可能比可用内存大几倍。我不希望从远程存储执行多个源文件的单个读取,也不希望不必要的文件访问。
欢迎任何想法!请问我问题所以我可以澄清一下我是否说得不够清楚。
答案 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中实现所需的一切,但不会出现问题:
1F 8B 08
那么它应该是gzip文件。修改强>
这不起作用,因为在传递给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)