Text-Processing

如何在不拆分多行記錄的情況下有效地拆分大型文本文件?

  • August 3, 2015

我有一個大文本文件(gz’ed 時約為 50Gb)。該文件包含4*N行或N記錄;也就是說,每條記錄由 4 行組成。我想將此文件拆分為 4 個較小的文件,每個文件的大小約為輸入文件的 25%。如何在記錄邊界拆分文件?

一種天真的方法是zcat file | wc -l獲取行數,將該數字除以 4,然後使用split -l <number> file. 但是,這會遍歷文件兩次,並且行計數非常慢(36 分鐘)。有沒有更好的辦法?

很接近,但不是我想要的。接受的答案也會計算行數。

編輯:

該文件包含 fastq 格式的測序數據。兩條記錄如下所示(匿名):

@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF

每條記錄的第一行以@.

編輯2:

zcat file > /dev/null需要 31 分鐘。

EDIT3: 只有第一行以@. 其他人都不會。見這裡。記錄需要保持有序。向結果文件中添加任何內容是不行的。

我認為您無法做到這一點-不可靠,也不是您要求的方式。問題是,檔案的壓縮率可能不會從頭到尾均勻分佈 - 壓縮算法將比其他部分更好地適用於某些部分。這就是它的工作原理。因此,您不能將拆分因素考慮到壓縮文件的大小。

更重要的是,gzip只是不支持儲存大小大於 4gbs 的壓縮文件的原始大小 - 它無法處理。因此,您無法查詢存檔以獲得可靠的大小 - 因為它會欺騙您。

4 行的東西 - 這很容易,真的。4 文件的東西 - 我只是不知道如何在不首先提取存檔以獲得其未壓縮大小的情況下可靠且均勻分佈地做到這一點。我不認為你可以,因為我試過了。

但是,您可以做的是為拆分輸出文件設置最大大小,並確保這些文件總是在記錄障礙處被打破。您可以輕鬆做到這一點。這是一個小腳本,它將通過提取gzip存檔來完成,並通過一些dd帶有特定參數的顯式管道緩衝區管道內容count=$rpt,然後將其傳遞lz4給動態解壓縮/重新壓縮每個文件。我還加入了一些小tee技巧,將每個段的最後四行也列印到 stderr。

(       IFS= n= c=$(((m=(k=1024)*k)/354))
       b=bs=354xk bs=bs=64k
       pigz -d </tmp/gz | dd i$bs o$b |
       while   read -r line _$((n+=1))
       do      printf \\n/tmp/lz4.$n\\n
       { {     printf %s\\n "$line"
               dd count=$c i$b o$bs
       }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
       } 3>&1| tail -n4 |tee /dev/fd/2 |
               wc -c;ls -lh /tmp/[gl]z*
       done
)

這將一直持續到它處理完所有輸入。它不會嘗試將其拆分為某個百分比(它無法獲得),而是按照每個拆分的最大原始字節數對其進行拆分。無論如何,您的問題的很大一部分是您無法在存檔中獲得可靠的大小,因為它太大了-無論您做什麼,都不要再這樣做了-這樣一來,拆分小於 4gbs , 也許。至少,這個小腳本使您能夠做到這一點,而無需將未壓縮的字節寫入磁碟。

這是一個精簡版的精簡版——它沒有添加所有的報告內容:

(       IFS= n= c=$((1024*1024/354))
       pigz -d | dd ibs=64k obs=354xk |
       while   read -r line _$((n+=1))
       do {    printf %s\\n "$line"
               dd count=$c obs=64k ibs=354xk
       }  |    lz4 -BD -9  >/tmp/lz4.$n
       done
)  </tmp/gz

它做的所有事情都和第一個一樣,主要是,它沒有那麼多話要說。此外,混亂更少,因此可能更容易看到正在發生的事情。

IFS=問題只是read每次迭代處理一行。我們這樣做read是因為我們需要在輸入結束時結束循環。這取決於您的記錄大小- 根據您的範例,每個記錄大小為 354 字節。gzip為了測試它,我創建了一個包含一些隨機數據的 4+gb存檔。

隨機數據是這樣得到的:

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
       (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
       tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
       sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
       paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null

…但也許您不必為此擔心太多,因為您已經擁有了所有數據。回到解決方案…

基本上pigz- 這似乎比解壓縮快一點zcat- 將未壓縮的流和dd緩衝區輸出到寫入塊中,該寫入塊的大小專門設置為 354 字節的倍數。該循環將read$line每次迭代中測試一次輸入是否仍然到達,printf然後printflz4另一個dd被呼叫以讀取特定大小為 354 字節的塊 - 以與緩衝dd過程同步 - 持續時間之前。由於最初的原因,每次迭代都會有一個簡短的讀取read $line- 但這沒關係,因為我們正在列印lz4- 我們的收集器程序 - 無論如何。

我已經對其進行了設置,因此每次迭代將讀取大約 1gb 的未壓縮數據並將該流內壓縮到大約 650Mb 左右。lz4比幾乎任何其他有用的壓縮方法都要快得多——這就是我在這裡選擇它的原因,因為我不喜歡等待。xz不過,可能會在實際壓縮方面做得更好。但是,關於 的一件事lz4是它通常可以以接近 RAM 的速度解壓縮 - 這意味著很多時候您可以lz4像將存檔寫入記憶體一樣快地解壓縮存檔。

大的每次迭代都會做一些報告。兩個循環都會列印dd關於傳輸的原始字節數和速度等的報告。大循環還將列印每個週期的最後 4 行輸入,以及相同的字節數,然後是ls我寫入lz4檔案的目錄。以下是幾輪輸出:

/tmp/lz4.1
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.838 s, 6.3 MB/s
@NTACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGC+TCTCTNCC
TACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGCTCTCTNCCGAGCTCAGTATGTTNNAAGTCCTGANGNGTNGCGCCTACCCGACCACAACCTCTACTCGGTTCCGCATGCATGCAACACATCGTCA
+
I`AgZgW*,`Gw=KKOU:W5dE1m=-"9W@[AG8;<P7P6,qxE!7P4##,Q@c7<nLmK_u+IL4Kz.Rl*+w^A5xHK?m_JBBhqaLK_,o;p,;QeEjb|">Spg`MO6M'wod?z9m.yLgj4kvR~+0:.X#(Bf
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1

/tmp/lz4.2
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.38 s, 6.3 MB/s
@NTTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGAC+CTTTTGCT
TTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGACCTTTTGCTGCCCTGGTACTTTTGTCTGACTGGGGGTGCCACTTGCAGNAGTAAAAGCNAGCTGGTTCAACNAATAAGGACNANTTNCACTGAAC
+
>G-{N~Q5Z5QwV??I^~?rT+S0$7Pw2y9MV^BBTBK%HK87(fz)HU/0^%JGk<<1--7+r3e%X6{c#w@aA6Q^DrdVI0^8+m92vc>RKgnUnMDcU:j!x6u^g<Go?p(HKG@$4"T8BWZ<z.Xi
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:35 /tmp/lz4.2

引用自:https://unix.stackexchange.com/questions/209938