30k以下のファイルがあります。各ファイルには〜100k行が含まれます。行にはスペースが含まれていません。個々のファイル内の行は並べ替えられ、複製されません。
私の目標:2つ以上のファイルにまたがるすべてのall重複行、および重複したエントリを含むファイルの名前も検索したい。
簡単な解決策はこれです:
cat *.words | sort | uniq -c | grep -v -F '1 '
そして私は走るでしょう:
grep 'duplicated entry' *.words
より効率的な方法がわかりますか?
すべての入力ファイルは既に並べ替えられているため、実際の並べ替え手順をバイパスし、_マージファイルをまとめてsort -m
を使用するだけです。
一部のUnixシステム(私の知る限りonly 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
)。ファイル名と行を端末に出力します。
一部のUnixシステムでは、30000のファイル名が、単一のユーティリティに渡すには長すぎる文字列に拡張されます(つまり、sort -m *.words
は、OpenBSDシステムではArgument list too long
で失敗します)。 Linuxでさえ、ファイルの数がはるかに多い場合、これについて文句を言うでしょう。
これは、一般的なケース(これはmany 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
からスクリプトに渡される個別に引用されたファイル名のリストに展開されます。
次に、tmpfile
に対してuniq -d
を実行して、重複するすべての行を取得します。
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 {} +
実行する「複雑な」処理はないため、-exec
から直接grep
を呼び出すことができます。 -exec
オプションはユーティリティコマンドを受け取り、見つかった名前を{}
に配置します。 +
が最後にある場合、find
は、ユーティリティの各呼び出しで現在のシェルがサポートするのと同じ数の引数を{}
の代わりに配置します。
完全にであるためには、どちらかを使用することができます
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
を使用して、常に一致するファイル名を出力します。最後のバリエーションでは、コマンドラインで1つ以上のファイルが指定されている場合、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
に一致する終了。 currentディレクトリのみを検索する場合は、-maxdepth 1
の後ろ、.
の前に-type f
を追加できます。
-print0
は、見つかったすべてのパス名が\0
(nul
)文字を区切り文字として出力されることを保証します。これは、Unixパスでは無効な文字であり、改行文字(または他の奇妙なもの)が含まれている場合でもパス名を処理できます。
find
は、その出力をxargs
にパイプします。
xargs -0
は\0
で区切られたパス名のリストを読み取り、指定されたユーティリティをこれらのチャンクで繰り返し実行します。これにより、シェルがあまりに文句を言わないように十分な引数でユーティリティが実行されます。 find
からの入力がなくなるまで、長い引数リスト。
xargs
によって呼び出されるユーティリティはsh
であり、スクリプトはコマンドラインで-c
フラグを使用して文字列として指定されます。
以下の引数を使用してsh -c '...some script...'
を呼び出すと、引数は$@
、最初の引数を除くでスクリプトで使用できるようになり、$0
(これは、たとえばtop
で見つけることができる「コマンド名」です。これが、実際のスクリプトの終わりの後に最初の引数として文字列sh
を挿入する理由です。文字列sh
はダミー引数であり、任意の単一の単語にすることができます(_
またはsh-find
を好む人もいます)。
個々のファイル内の行はソートされ、複製されません。
つまり、おそらくsort -m
:
-m, --merge
merge already sorted files; do not sort
これを行う他の明白な代替策は、配列の行を収集してそれらを数える単純なawk
です。しかし、 @ dave_thompson_085 がコメントしているように、これらの300万行(または固有の行は多数あります)を格納するにはかなりの量のメモリが必要になるため、うまく機能しない可能性があります。
最適化sort
+ uniq
ソリューション:
sort --parallel=30000 *.words | uniq -d
--parallel=N
-同時に実行するソートの数をN
に変更します-d, --repeated
-グループごとに1つずつ、重複する行のみを印刷しますAwkを使用すると、1つの短いコマンドですべてのファイルのすべての繰り返し行を取得できます。
$ awk '_[$0]++' *.words
ただし、行が3回以上存在する場合は、行が繰り返されます。
最初の重複のみを取得する解決策があります:
$ awk '_[$0]++==1' *.words
(リピートが少ない場合)かなり高速ですが、すべての行をメモリに保持するために大量のメモリを消費します。たぶん、実際のファイルと繰り返しに応じて、3つまたは4つのファイルを最初に試してください。
$ awk '_[$0]++==1' [123]*.words
それ以外の場合は、次のことができます。
$ sort -m *.words | uniq -d
Uniqの繰り返し行を印刷します。