2016-09-02 136 views
2

屏蔽問題:我需要從單個大文本文件(input.txt,100+)中找到並掩蓋(即替換爲「XXX」)某些術語(單詞/表達式) MB)。我需要找到的術語(10K +)保存在一個文件(to_mask.txt)中。我如何以有效的方式執行此操作?從大文件中查找並替換

我想分兩個步驟這樣做的:首先找到實際包含的條款

grep -Ff to_mask.txt -o -n input.txt 

下一頁經過輸出行和做實際的更換(名詞 - >「XXX」)。

這似乎有點乏味,可以以更聰明的方式完成嗎?

歡迎任何基本命令的組合(grep,sed,awk,one-line-perl)!

UPDATE:

馬科斯,Kenavoz,埃德莫頓和Sobrique都給予可行的解決方案,謝謝! 我選擇Sobrique的解決方案作爲我接受的解決方案,因爲它比其他數據快得多。它可能無法處理一些特殊情況,但我相信它可以擴展到能夠這樣做,再加上它在目前的狀況下執行目前的工作。

UPDATE2:

僅供參考,這裏是由Kenavoz提供的解決方案:

sed -f <(sed 's~^~s\~~;s~$~\~XXX\~~' to_mask.txt) input.txt 
+0

我覺得你首先應該熟悉[斯肯索普問題]的概念(https://en.wikipedia.org/wiki/Scunthorpe_prob lem),然後用多於一個班輪來解決這個問題(尤其是如果涉及其他最終用戶的話)。掩碼的過濾通常以0 b 5 C u r 1 + y循環,計算機很難找到,而人類往往能夠閱讀。 –

+0

感謝@Draw斯隆,我對垃圾郵件問題有些熟悉。但是,我需要儘可能少地使用庫(它將在客戶端運行),因此爲了簡單起見,我可能會失去一點效率。 – vivasra

回答

1

我想我會解決它像這樣:

#!/usr/bin/perl 
use strict; 
use warnings; 

#read the mask file into memory. 
open (my $mask, '<', "to_mask.txt") or die $!; 
chomp (my @terms = <$mask>); 
close ($mask); 
#build a really big regex 
#map quotemeta means handling metachars. 
#if you _know_ there are none, then you can omit this. 
#or if you actually want to be able to use regex in your terms file. 
my $mega_regex = join "|", map { quotemeta } @terms; 

    #compile it into a non-capturing regex, and use \b to anchor on word boundaries. 
    #You don't want to be filtering out Scunthorpe ... 
    $mega_regex = qr/\b(?:$mega_regex)\b/; 

#<> means iterate 'stdin' or 'files specified on command line'. 
#just like how grep/sed/awk does it 
while (<>) { 
    s/$mega_regex/XXX/g; 
    print; #to STDOUT 
} 
+0

謝謝。非捕獲正則表達式似乎不起作用,但如果我將它評論出來,它就像魅力一樣。順便說一句,如果我的掩碼術語是「a」,「b」和「c」,對於非捕獲$ mega_regex:(?^:\ b(?:a | b | c)\ b) – vivasra

+0

Hrm,不確定哪個位不工作 - 但是,'(?:pattern)'是一個非捕獲組。這種情況並不重要,但它是多餘的。我得到了合理的結果(數據集小得多):'(?^:\ b(?:fish | foo | moo)\ b)' – Sobrique

+0

不確定,語法似乎沒有問題。但正如你所說,這部分是多餘的,所以你的解決方案完美無缺。 – vivasra

1

你可以嘗試:

while read mask; do sed -i "s/$mask/XXX/g" input.txt; done < to_mask.txt 

可能不是最有效的解決方案在世界上,但它應該做的工作... :-)
作爲獎金,它只是使用shell和sed命令

UPDATE

這是一個解決方案,它是快(其寫入大input.txt文件只有一次)。 它首先生成一個fullmask變量,因爲所有掩碼的連接由|OR運算符)分開。 雖然,我覺得比第一次發佈的解決方案不太清楚... :-)

fullmask=""; cat to_mask.txt | while read mask; do fullmask="$fullmask|$mask"; done && sed -i "s/$fullmask/XXX/g" input.txt 

請不是我沒有測試此解決方案,它可以包含一些問題...
更多,它假定to_mask.txt做不包含任何|也沒有任何/字符...

更新2

對不起! sed不支持正則表達式,替換爲...:-(
我想出了這個解決方案使用perl,更噁心,但絕對工作(只是測試一個簡單的用例):

fullmask="("; while read mask; do if [ "$fullmask" != "(" ]; then fullmask="$fullmask|$mask"; else fullmask="$fullmask$mask"; fi; done < to_mask.txt; fullmask="$fullmask)"; perl -p -i -e "s/$fullmask/XXX/g" input.txt 
+1

只要to_mask.txt文件不包含需要反斜槓的特殊字符的行,就會正常工作......如果是這樣,那麼to_mask.txt可以事先被處理,以便可以像上面那樣使用它。 – louigi600

+0

@ louigi600:你是對的...... OP可以改變分隔符,以避免出現'/'問題......但是隻有知道'to_mask.txt'文件的內容並且使用不存在的字符(如果有...)。 – MarcoS

+2

10,000次迭代讀/寫同一個文件?啊。 – Sobrique

1

你只需要1個命令:

awk 'NR==FNR{t=(t?t"|":"")$0;next} {gsub(t,"XXX")} 1' to_mask.txt input.txt 
+1

謝謝,您的評論也在上面! – vivasra