如何在許多大文件中查找重複行?
我有大約 30k 個文件。每個文件包含約 100k 行。一行不包含空格。單個文件中的行被排序和免費複製。
我的目標:我想找到兩個或多個文件中的所有重複行,以及包含重複條目的文件的名稱。
一個簡單的解決方案是:
cat *.words | sort | uniq -c | grep -v -F '1 '
然後我會執行:
grep 'duplicated entry' *.words
你看到更有效的方法了嗎?
由於所有輸入文件都已經排序,我們可以繞過實際的排序步驟,僅
sort -m
用於將文件合併在一起。在某些 Unix 系統上(據我所知只有Linux),這可能就足夠了
sort -m *.words | uniq -d >dupes.txt
將重複的行寫入文件
dupes.txt
。要查找這些行來自哪些文件,您可以執行以下操作
grep -Fx -f dupes.txt *.words
這將指示將( ) 中
grep
的行視為固定字元串模式( )。還要求整行從頭到尾完美匹配 ( )。它將文件名和行列印到終端。dupes.txt``-f dupes.txt``-F``grep``-x
非 Linux Unices(甚至更多文件)
在某些 Unix 系統上,30000 個文件名將擴展為一個字元串,該字元串太長而無法傳遞給單個實用程序(這意味著
sort -m *.words
將失敗Argument list too long
,並在我的 OpenBSD 系統上執行此操作)。如果文件數量大得多,即使 Linux 也會抱怨這一點。尋找騙子
這意味著在一般情況下(這也適用於超過30000 個文件),必須“分塊”排序:
rm -f tmpfile find . -type f -name '*.words' -print0 | xargs -0 sh -c ' if [ -f tmpfile ]; then sort -o tmpfile -m tmpfile "$@" else sort -o tmpfile -m "$@" fi' sh
或者,創建
tmpfile
沒有xargs
:rm -f tmpfile find . -type f -name '*.words' -exec sh -c ' if [ -f tmpfile ]; then sort -o tmpfile -m tmpfile "$@" else sort -o tmpfile -m "$@" fi' sh {} +
這將在目前目錄(或以下)中找到名稱匹配的所有文件
*.words
。對於一次適當大小的這些名稱塊,其大小由xargs
/確定find
,它將它們合併到排序tmpfile
文件中。如果tmpfile
已經存在(除了第一個塊之外的所有文件),該文件也將與目前塊中的其他文件合併。根據文件名的長度和命令行的最大允許長度,這可能需要內部腳本的 10 多次單獨執行(find
/xargs
將自動執行此操作)。“內部”
sh
腳本,if [ -f tmpfile ]; then sort -o tmpfile -m tmpfile "$@" else sort -o tmpfile -m "$@" fi
用於
sort -o tmpfile
輸出到(即使這也是 的輸入也tmpfile
不會覆蓋)和進行合併。在這兩個分支中,將擴展為從或傳遞給腳本的單獨引用的文件名列表。tmpfile``sort``-m``"$@"``find``xargs
然後,繼續執行
uniq -d
以tmpfile
獲取所有重複的行:uniq -d tmpfile >dupes.txt
如果您喜歡“DRY”原則(“不要重複自己”),您可以將內部腳本編寫為
if [ -f tmpfile ]; then t=tmpfile else t=/dev/null fi sort -o tmpfile -m "$t" "$@"
或者
t=tmpfile [ ! -f "$t" ] && t=/dev/null sort -o tmpfile -m "$t" "$@"
哪兒來的?
出於與上述相同的原因,我們無法
grep -Fx -f dupes.txt *.words
找到這些重複的來源,因此我們find
再次使用:find . -type f -name '*.words' \ -exec grep -Fx -f dupes.txt {} +
由於沒有“複雜”的處理要完成,我們可以
grep
直接呼叫 from-exec
。該-exec
選項採用實用程序命令並將找到的名稱放在{}
. 在+
最後,find
將在每次呼叫實用程序時放置{}
目前 shell 支持的盡可能多的參數。為了完全正確,人們可能想要使用
find . -type f -name '*.words' \ -exec grep -H -Fx -f dupes.txt {} +
或者
find . -type f -name '*.words' \ -exec grep -Fx -f dupes.txt /dev/null {} +
確保文件名始終包含在
grep
.第一個變體用於
grep -H
始終輸出匹配的文件名。如果在命令行上給出了多個文件grep
,則最後一個變體使用將包含匹配文件的名稱的事實。
grep
這很重要,因為發送到from的最後一塊文件名find
實際上可能只包含一個文件名,在這種情況下grep
,不會在結果中提及它。獎勵材料:
剖析
find
++命令xargs
:sh
find . -type f -name '*.words' -print0 | xargs -0 sh -c ' if [ -f tmpfile ]; then sort -o tmpfile -m tmpfile "$@" else sort -o tmpfile -m "$@" fi' sh
find . -type f -name '*.words'
將簡單地從目前目錄(或以下)生成路徑名列表,其中每個路徑名是正常文件(-type f
) 的路徑名,並且在末尾有一個文件名組件匹配*.words
. 如果只搜尋目前目錄,可以-maxdepth 1
在.
, before之後添加-type f
。
-print0
將確保所有找到的路徑名都以\0
(nul
) 字元作為分隔符輸出。這是一個在 Unix 路徑中無效的字元,它使我們能夠處理路徑名,即使它們包含換行符(或其他奇怪的東西)。
find
將其輸出通過管道傳輸到xargs
.
xargs -0
將讀取\0
- 分隔的路徑名列表,並使用其中的塊重複執行給定的實用程序,確保使用足夠的參數執行實用程序,不會導致 shell 抱怨參數列表太長,直到沒有更多輸入從find
.呼叫的實用程序
xargs
是在命令行上使用其標誌sh
作為字元串給出的腳本。-c
當
sh -c '...some script...'
使用後面的參數呼叫時,參數將可用於腳本中$@
,除了第一個參數,它將被放置在中$0
(這是你可能會發現的“命令名稱”,例如top
,如果你足夠快的話)。這就是為什麼我們在實際腳本結束後插入字元串sh
作為第一個參數的原因。該字元串sh
是一個虛擬參數,可以是任何單個單詞(有些人似乎更喜歡_
orsh-find
)。