
約30,000個のファイルがあります。各ファイルには約100,000行が含まれています。行にはスペースは含まれません。単一ファイル内の行はソートされ、重複しません。
私の目標:すべてを探したいみんな複数のファイルに重複した行があり、重複したエントリを含むファイル名。
簡単な解決策は次のとおりです。
cat *.words | sort | uniq -c | grep -v -F '1 '
次に、次を実行します。
grep 'duplicated entry' *.words
より効率的なアプローチが見えますか?
答え1
すべての入力ファイルがすでにソートされているため、実際のソートステップをスキップして次のものをsort -m
使用できます。マージファイルを一緒に入れてください。
一部のUnixシステムでは(私が知る限りただLinux)、これで十分だと思います
sort -m *.words | uniq -d >dupes.txt
fileに重複した行を書き込みますdupes.txt
。
この行がどのファイルから来たかを見つけるには:
grep -Fx -f dupes.txt *.words
grep
これは、()内の行が次のように処理されるように指示します。dupes.txt
-f dupes.txt
固定文字列パターン(-F
)。grep
また、行全体が最初から最後まで完全に一致する必要があります(-x
)。ファイル名と行を端末に印刷します。
非Linux Unices(でももっと文書)
一部のUnixシステムでは、30000個のファイル名が単一のユーティリティに渡されるには長すぎる文字列に拡張されています(これは私のOpenBSDシステムが実行するsort -m *.words
印刷出力が失敗することを意味します)。Argument list too long
ファイルの数がはるかに多い場合は、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
から直接呼び出すことができます-exec
。この-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
常に一致するファイル名を出力するために使用されます。最後のバリエーションは次の事実を使用します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
後ろに追加.
するか、前に追加できます-type f
。
-print0
\0
見つかったすべてのパス名が(nul
)文字を区切り文字として出力していることを確認してください。これはUnixパスで無効な文字であり、改行文字(またはその他の奇妙な内容)が含まれていてもパス名を処理できます。
find
出力をxargs
。
xargs -0
A - 区切りパス名のリストを読み、\0
そのユーティリティはその中のチャンクを使用して繰り返し実行されます。したがって、引数リストが長すぎるとシェルが文句を言わないように、十分な引数を使用してユーティリティを実行します。もう入力がありませんfind
。
呼び出されるユーティリティは、そのフラグを使用してコマンドラインに文字列として提供されるスクリプトxargs
です。sh
-c
後続のパラメータと一緒に呼び出されると、sh -c '...some script...'
これらのパラメータをスクリプトで使用できます$@
。最初のパラメータを除いて、 に配置されます(たとえば、十分に高速な場合に見つけることが$0
できる「コマンド名」です)。top
これがsh
実際のスクリプトの最後に最初のパラメータとして文字列を挿入する理由です。文字sh
列仮想論争任意の単一の単語にすることができます(一部の人はまたはを_
好むようですsh-find
)。
答え2
単一ファイル内の行はソートされ、重複しません。
これは、次の目的を見つけることができることを意味しますsort -m
。
-m, --merge
merge already sorted files; do not sort
もう一つの明確な選択肢は、単にawk
配列の行を集めて数を数えることです。しかし〜に応じてまさかコメントによれば、これらの30億行(または一意の行がどれだけ多いか)は保存に多くのメモリを占有するため、正しく機能しない可能性があります。
答え3
awkを使用すると、1つの短いコマンドですべてのファイルのすべての重複行を取得できます。
$ awk '_[$0]++' *.words
ただし、行が3回以上存在する場合、その行は重複します。
最初のレプリカのみを取得するソリューションがあります。
$ awk '_[$0]++==1' *.words
(繰り返し回数が少ない場合)速度は速くなければなりませんが、すべての行をメモリに保持するために多くのメモリを消費します。実際のファイルと繰り返し回数に応じて、まず3〜4個のファイルを試してみてください。
$ awk '_[$0]++==1' [123]*.words
それ以外の場合は、次のようにできます。
$ sort -m *.words | uniq -d
これにより、一意の反復行が印刷されます。
答え4
comm
は、この種の作業のための別のツールです。唯一の注意点は、事前にソートされたデータソースが必要であることです。<(...)
この構文は、ほとんどの最新のシェルで使用できます。
# suppress common lines
comm -3 <(echo "1\n2") <(echo "3\n1"| sort)
2
3
# display common lines
comm -12 <(echo "1\n2") <(echo "1\n3")
1