Shell-Script

zgrep 在多個文件中的性能緩慢

  • August 23, 2021

我有一個9.8GB的​​ gzip 文件 A.gz,而我擁有的其他文件是79MB B.txt,每行都有一些文本。我想在 A.gz 中 grep B 的文本並寫入一個新文件。

最初,我使用了這個命令

zgrep -f B.txt A.gz > C.xml

但是這個命令被掛了,它創建了一個空的 C.xml 很長一段時間。

然後在Google搜尋之後我才知道,因為 B.txt 很大,當它將文本保存在緩衝區中時它會掛起。

所以我將文本文件拆分為 20000 個文本

split -l 20000 -a 4 B.txt B

我創建了諸如 Baaaa、Baaab 之類的文件….

然後遍歷每個文件

cd B
for f in B*; do
 zgrep -f "$f" ../A.gz >> C.xml
done

它非常慢並且仍在執行。

有什麼更好的方法嗎?

gunzip 壓縮 gz 文件會提高性能嗎?

更新

我試過 -F

zgrep -F -f "$f" ../A.gz >> C.xml

這有點快,但仍然需要其他選擇

我有xml之類的

<root>
  <source>source1</source>
  <Id>123</Id>
  <category>ABC</category>
</root>
<root>
   <source>source2</source>
   <Id>123</Id>
   <category>XYZ</category>
</root>

這裡 id 相同 123 但類別不同 ABC 和 XYZ

(輸入是有限的類別集,例如 ABC、DEF、GHI、JKLM、NOP)最初我的類別為 ABC,因此基於類別 ABC 我找到了它的 id 即 123 像這樣我繼續寫屬於這些的所有 id將類別輸入到新文件,即 B.txt(id 列表),如下所示

zgrep -E 'ABC|DEF|GHI|JKLM|NOP' A.gz | sed -n 's:.*<Id>\(.*\)</Id>.*:\1:p' | uniq > B.txt

稍後我遍歷這個id並獲取所有xml,這樣我得到了屬於id 123的類別ABC和XYZ的xml標籤

一個 79MByte 的 grep “字元串” 使用起來會很痛苦。是B.txt真正的正則表達式的行還是它們固定相同的字元串?如果它們是固定字元串,它們在A.gz整行中是否相同?未壓縮中的多少行A.gz預計與 中的行匹配B.txt

模式匹配建議

如果行中的行B.txt確實是正則表達式或行的子字元串,則A.gz可能會被迫使用HyperScan之類的東西,它旨在處理大量正則表達式。如果您有磁碟空間,您可以解壓縮A.gz並讓 HyperScan 開始工作(您甚至可以讓 shell 在 HyperScan 搜尋時動態解壓縮)。另一種嘗試是ripgrep

全線匹配建議

如果您正在處理固定的完整行字元串,B.txt並且未壓縮的字元串A.gz包含相對較小的匹配行(比如說大約 100MB),那麼您最好編寫一個程序來進行預處理A.gz

  • 您可以散列每一行B.txt並記住散列
  • 然後,您檢查未壓縮A.gz散列中的任何行是否與您之前的任何散列相同。如果是這樣,您列印出該行(例如 into C.txt)以供進一步處理
  • 你現在做最後一次通過,你更嚴格地檢查每一行是否B.txtC.txt裡面(反之亦然 - 取決於哪個文件更小)

一些用於進行初始近似過濾的程式碼可能是這樣的:

# Do a quick APPROXIMATE filter of lines in FILENEEDLES that are also in
# FILEHAYSTACK
import sys

def main():
   if len(sys.argv) < 2:
       print("usage: %s FILENEEDLES FILEHAYSTACK" % sys.argv[0])
       exit(1)

   first_filename = sys.argv[1]
   second_filename = sys.argv[2]

   line_hashes = set()

   with open(first_filename, "r") as f:
       for line in f:
           line_hashes.add(hash(line))

   with open(second_filename, "r") as f:
       for line in f:
           if hash(line) in line_hashes:
               sys.stdout.write(line)

if __name__ == "__main__":
   main()

例如:

$ echo -e '1\n2\n3' > B.txt
$ echo -e '2\n3\n4\5' | gzip > A.gz
$ ./approxfilter.py B.txt <(gzip -dc A.gz) > candidates.txt
$ cat candidates.txt
2
3

您現在需要檢查 Candidate.txt 以查看行輸出是否完全匹配B.txt(但希望這是一個更小且更容易的問題,如果候選行的數量“小”,您甚至可以修改上面的程序來完成所有操作"並且完全在記憶體中可以保存的範圍內)。(發問者後來在評論中澄清說他們沒有使用全行長度的字元串,所以這種方法不起作用)

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