多くの大きなファイルで重複する行を見つける方法は?


9

30k以下のファイルがあります。各ファイルには〜100k行が含まれます。行にはスペースが含まれていません。個々のファイル内の行はソートされ、複製されません。

私の目標:2つ以上のファイルにまたがるすべての重複行と、重複したエントリを含むファイルの名前も検索したいです。

簡単な解決策はこれです:

cat *.words | sort | uniq -c | grep -v -F '1 '

そして私は走るでしょう:

grep 'duplicated entry' *.words

より効率的な方法がわかりますか?

回答:


13

すべての入力ファイルは既に並べ替えsort -mられいるため、実際の並べ替え手順を省略して、ファイルをマージするためにのみ使用できます。

一部のUnixシステム(私の知る限り Linux のみ)では、これで十分な場合があります

sort -m *.words | uniq -d >dupes.txt

複製された行をファイルに書き込むためdupes.txt

これらの行がどのファイルからのものかを見つけるには、次のようにします

grep -Fx -f dupes.txt *.words

これはgrepdupes.txt-f dupes.txt)の行を固定文字列パターン-F)として扱うように指示します。grepまた、行全体が最初から最後まで完全に一致する必要があります(-x)。ファイル名と行を端末に出力します。

Linux以外のUnices(またはさらに多くのファイル)

一部のUnixシステムでは、30000のファイル名が単一のユーティリティに渡すには長すぎる文字列に拡張されます(つまり、OpenBSDシステムではでsort -m *.words失敗しArgument list too longます)。Linuxでさえ、ファイル数がはるかに多い場合、これについて不満を言うでしょう。

だまし絵を見つける

一般的な場合(これはまた、意志と仕事していることをこの意味多く:ちょうど30000ファイル以上)、1ソート「チャンク」にあり

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/ によって決定さfindtmpfileます)の場合、それらはソートされたファイルにマージされます。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マージを行うため。両方のブランチで、または"$@"からスクリプトに渡される個別に引用されたファイル名のリストに展開されます。findxargs

次に、実行uniq -dtmpfileて複製されたすべての行を取得します。

uniq -d tmpfile >dupes.txt

"DRY"原則( "Do n't Repeat Yourself")が好きな場合は、内部スクリプトを次のように書くことができます。

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から直接呼び出すことができ-execます。この-execオプションはユーティリティコマンドを受け取り、見つかった名前をに配置し{}ます。では+最後に、findの代わりに多くの引数として配置されます{}ユーティリティを呼び出すたびに、現在のシェルの支持体として。

であるためには、完全に正しい、1のいずれかを使用することもできます

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から送信されたファイル名の最後のチャンクに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.、の前に追加でき-type fます。

-print0見つかったすべてのパス名が区切り文字として\0nul)文字を使用して出力されるようにします。これはUnixパスでは無効な文字であり、改行文字(または他の奇妙なもの)が含まれている場合でもパス名を処理することができます。

find出力をにパイプしますxargs

xargs -0\0パス名の-区切りリストを読み取り、指定されたユーティリティをこれらのチャンクで繰り返し実行します。これにより、入力がなくなるまで、シェルが長すぎる引数リストについて文句を言わないように十分な引数でユーティリティが実行されます。からfind

呼び出されるユーティリティがxargsありsh、その使用して文字列として、コマンドラインで指定されたスクリプトを使用して-cフラグ。

起動するとsh -c '...some script...'、次の引数で、引数は、スクリプトを利用できるようになり$@最初の引数を除いに配置されます、$0(これはあなたが例えばのスポットことを「コマンド名」であるtopあなたが速い十分にある場合)。これがsh、実際のスクリプトの終了後、最初の引数として文字列を挿入する理由です。文字列sh仮引数であり、任意の単一の単語にすることができます(一部の_ユーザーはまたはを好むようですsh-find)。


シェルスクリプトの最初のブロックの最後で、何を使用していfi' shますか?
dan

@danielAzuelosこれfiは、if「内部」shシェルスクリプトのステートメントの終わりです。'シェルスクリプト(スクリプト全体が単独で引用された文字列である)ことを終了します。sh内部スクリプトに渡される$0(の一部ではない$@ファイル名を含むであろう)。この場合、そのsh文字列は実際には任意の単語です。sh最後に除外すると、最初のファイル名が渡され$0、内部シェルスクリプトが実行している処理の一部にはなりません。
クサラナンダ

8

個々のファイル内の行はソートされ、複製されません。

つまり、次のような用途が見つかるかもしれませんsort -m

 -m, --merge
        merge already sorted files; do not sort

これを行う他の明白な代替策awkは、配列の行を収集してそれらを数えることです。しかし、@ dave_thompson_085がコメントしたように、これらの300万行(または多くのユニークな行がある)は、かなりの量のメモリを格納するためにかなりの量のメモリを必要とするため、うまく機能しない可能性があります。


3

awkを使用すると、1つの短いコマンドですべてのファイルのすべての繰り返し行を取得できます。

$ awk '_[$0]++' *.words

ただし、行が3回以上存在する場合は、行が繰り返されます。
最初の重複のみを取得する解決策があります:

$ awk '_[$0]++==1' *.words

(リピートが少ない場合)かなり高速ですが、すべての行をメモリに保持するために大量のメモリを消費します。たぶん、実際のファイルと繰り返しに応じて、3つまたは4つのファイルを最初に試してください。

$ awk '_[$0]++==1' [123]*.words

それ以外の場合は、次のことができます。

$ sort -m *.words | uniq -d

uniqの繰り返し行を印刷します。


2
以下のための1sort -m * | uniq -d
ジェフ・シャラー

awkを使用すると、繰り返しを回避できますが'x[$0]++==1'、実際には大量のメモリが必要になります。3Gの行に1Gの個別の値があり、awkが(おそらく短すぎる)文字列をuninit値にマッピングするhasharrayエントリに50バイトを必要とする場合、それは50GBです。ソートされた入力の場合、uniq -d手動で行うことができますawk '$0==p&&n++==1;$0!=p{p=$0;n=1}'が、なぜわざわざですか?
dave_thompson_085

@ dave_thompson_085のコンセプトをありがとう==1、素晴らしいアイデア。
アイザック

それぞれ80文字の100000行で重複がない 30000個のファイルを想定すると、awk2.4E11バイト(223 GiB)を格納する必要があります。
クサラナンダ

sort -m *.words | uniq -dよく働く!プロセスの後で、grep重複したエントリを含むファイルを見つけるために実行します。重複したエントリを含む少なくとも1つのファイル名を印刷する方法がわかりますか?
Lars Schneider

3

最適化sort+ uniqソリューション:

sort --parallel=30000 *.words | uniq -d
  • --parallel=N -同時に実行するソートの数を N
  • -d, --repeated -各グループに1つずつ、重複する行のみを印刷します
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.