2

背景:
Linux上のPython2.6.6。DNA配列分析パイプラインの最初の部分。
マウントされたリモートストレージ(LAN)からgzipで圧縮された可能性のあるファイルを読み取りたいのですが、gzipで圧縮されている場合は; ストリームにgunzipで圧縮し(つまり、を使用gunzip FILENAME -c)、ストリーム(ファイル)の最初の文字が「@」の場合は、そのストリーム全体を、標準入力で入力を受け取るフィルタリングプログラムにルーティングします。それ以外の場合は、ローカルのファイルに直接パイプします。ディスク。リモートストレージからのファイルの読み取り/シークの数を最小限に抑えたい(ファイルを1回通過するだけで不可能ではないでしょうか?)。

サンプル入力ファイルの内容、FASTQ形式の1つのレコードに対応する最初の4行:

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

フィルタリングプログラムにパイプされるべきではないファイルには、次のようなレコードが含まれています(FASTA形式の1つのレコードに対応する最初の2行)。

>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を新しいストリームに追加します。この新しいストリームは、フィルターのPopenstdinに送られます。

ファイルサイズは、使用可能なメモリの数倍になる場合があることに注意してください。リモートストレージからのソースファイルの複数の読み取りを実行したくないし、不要なファイルアクセスを実行したくない。

どんなアイデアでも大歓迎です!十分に明確にしなかったかどうかを明確にするために、質問をしてください。

4

2 に答える 2

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()

アイデアは、ソースコマンドの出力から1文字を読み取り、を使用して元の出力を再作成し(printf '\xhh' && cat)、ピークを効果的に実装することです。置換ストリームは、シェルに任せて、重い物を持ち上げることを指定shell=Trueします。データは常にパイプラインに残り、メモリに完全に読み込まれることはありません。シェルのサービスは、ユーザーが指定したファイル名を含む呼び出しではなく、ピークバイトの未読を実装する単一の呼び出しに対してのみ要求されることに注意してください。その時点でも、バイトは16進数にエスケープされ、呼び出し時にシェルがバイトを壊さないようにします。PopencatPopenprintf

peekコードをさらにクリーンアップして、ピークされたコンテンツと置換を返すという名前の実際の関数を実装できますnew_source

于 2012-10-07T21:20:30.390 に答える
0

Pythonでシェルコマンドをラップすることは意味がありません。ただし、Pythonで必要なすべてを実現できますが、次のことを実行する必要はありません。

  1. 入力ファイルを開き、最初の3バイトを読み取ります。それらが等しい場合1F 8B 08は、gzipファイルである必要があります。
  2. ファイルマーカーをリセット
  3. gzipファイルまたは読み取りファイルの場合は、ファイルの内容をzlib.decompress()に渡します
  4. 必要に応じてフィルター機能に渡す
  5. 結果をファイルに書き込む

編集

zlibに渡す前にgzipヘッダーを削除する必要があるため、これは機能しません。fh.seek(0)ただし、ファイルがgzip(DEFLATE圧縮あり)であることを確認したい場合は、最初の3バイトをチェックし、を実行してファイルをgzip.open()に渡すことは可能です。

ファイルをgzipに渡すだけで、ファイルが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)  
于 2012-10-07T21:28:17.023 に答える