2012-10-07 69 views
2

背景:
Python 2.6.6在Linux上。 DNA序列分析流水線的第一部分。
我想從已安裝的遠程存儲器(LAN)讀取可能的gzip文件,如果它是gzip的;將它壓縮到一個流(即使用gunzip FILENAME -c),如果流(文件)的第一個字符是「@」,則將整個流路由到一個過濾程序,該過程程序在標準輸入上接受輸入,否則只需將其直接輸入到文件本地磁盤。我想盡量減少從遠程存儲中讀取/查找文件的次數(只需通過文件一次就不可能?)。在Python中窺視Popen流水線

內容的一個例子的輸入文件的,對應於一個記錄在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

使用[gzip的(http://docs.python.org/library /gzip.html#module-gzip)模塊而不是外部'gzip'應該給你更多的靈活性。 –

+0

@PedroRomano是的,它可能。我擔心我通過文件訪問的次數。這將成爲在超級計算機系統上執行的管道的數據採集步驟的一部分,並且可能會同時在多個節點上運行,並且太多的文件系統調用可能導致遠程文件服務器無法訪問。 – user1727089

回答

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=TruePopen,將其留給外殼和cat來完成繁重工作。數據始終保持在流水線中,永遠不會完全讀入內存。請注意,shell的服務僅針對Popen的單個調用請求,該調用實現了讀取被偷看的字節的非讀取,而不涉及涉及用戶提供的文件名的調用。即使在那時,該字節也會轉義爲十六進制,以確保在調用printf時shell不會破壞它。

該代碼可以進一步清理以實現名爲peek的實際函數,該函數返回窺視內容和替換new_source

+0

這是我猜想的做法,但它應該可以正常工作。我會嘗試一些事情,然後回到你的帖子,如果它變成最好的。我通常不會在命令行中使用用戶輸入來運行真正的shell(用戶可以設置輸出文件)。他們總是設法搞砸事件,除非你可能會把這個評論主題帶到一個偏離主題的位置,並且有一種消毒這種輸入的好方法? – user1727089

+0

感謝您的讚揚。 :)嚴重的是,儘管代碼可能看起來像是黑客,但這種解決方案在內存消耗方面效果很好,並且需要對現有代碼進行最少量的更改,這些代碼已經調用外部命令進行解壓縮和過濾。 「正確」解決方案需要對代碼進行重新安排,以便使用'zlib'並手動將數據提供給過濾器,這可能會減慢過程速度。這表明那些提出這種解決方案的人沒有提供工作代碼。 – user4815162342

+0

我現在更新了代碼以避免'shell = True'與用戶提供的輸入。 – user4815162342

0

它沒有意義包裹在Python shell命令。你可以在Python中實現所有你需要的東西,但是不會脫殼:

  1. 打開輸入文件並讀取前3個字節。如果它們等於1F 8B 08那麼它應該是gzip文件。
  2. 復位文件標記
  3. 通行證文件內容zlib.decompress()如果它是一個gzip文件或讀取文件
  4. 如果需要
  5. 寫入結果到文件
  6. 通過濾功能

編輯

這不會工作,因爲在傳遞給zlib之前,gzip頭文件需要被剝離。但是,如果您想確保文件是gzip(使用DEFLATE壓縮),則可能會檢查前3個字節,執行fh.seek(0)並將文件傳遞到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) 
+0

閱讀魔法是一種檢測文件類型的不好方法(特別是因爲我懷疑這是正確的魔法)。我認爲你應該用gzip打開文件,如果失敗則回退到常規閱讀。 – nneonneo

+0

@nneonneo你有一點可以將文件傳遞給gzip open方法並捕獲一個異常(或者只是默默地做正確的事情),但是你對其他所有事情都是錯誤的。爲什麼你不做一些研究,並在對我的過程產生懷疑之前找出gzip標題格式? –

+0

gzip魔術只有'1F 8B';下一個值是可能隨時間變化的壓縮方法。 :) – nneonneo