効率的にマージ/ソート/ユニークな多数のテキストファイル


8

私は素朴なことを試しています:

$ cat * | sort -u > /tmp/bla.txt

これは失敗します:

-bash: /bin/cat: Argument list too long

したがって、(巨大な一時ファイルを作成する)のようなばかげた解決策を避けるために:

$ find . -type f -exec cat {} >> /tmp/unsorted.txt \;
$ cat /tmp/unsorted.txt | sort -u > /tmp/bla.txt

私は使用してファイルを1つずつ処理することができましたが(これにより、メモリ消費が削減され、ストリーミングメカニズムに近づくはずです)。

$ cat proc.sh
#!/bin/sh
old=/tmp/old.txt
tmp=/tmp/tmp.txt
cat $old "$1" | sort -u > $tmp
mv $tmp $old

その後に続く:

$ touch /tmp/old.txt
$ find . -type f -exec /tmp/proc.sh {} \;

より単純なUNIXスタイルの代替品はcat * | sort -uありますMAX_ARGか:ファイル数に達したとき?このような一般的なタスク用の小さなシェルスクリプトを書くのは、やりがいがあります。


2
連結はまったく必要ですか?sort複数のファイル入力に対してそれを自動的に行いますが、それからsort -u *失敗するとArgument list too long思います
Sundeep

回答:


8

GNU sort、およびprintfが組み込まれているシェル(一部のバリアントを除いて、今日ではすべてのPOSIXのようなものpdksh):

printf '%s\0' * | sort -u --files0-from=- > output

ここでの問題は、そのパイプラインの2つのコンポーネントが同時に独立して実行されるため、左側のコンポーネントが*グロブを展開するときまでに、右側のコンポーネントがoutputファイルをすでに作成していて、問題が発生している可能性があることです(-uここにはないかもしれません)。outputあなたが(別のディレクトリに出力行くを持っているしたい場合ので、入力と出力ファイルの両方になります> ../output例えば)、または確認グロブは、出力ファイルと一致していませんします。

このインスタンスでそれに対処する別の方法は、それを書くことです:

printf '%s\0' * | sort -u --files0-from=- -o output

このようにして、それは書き込み用にsort開いoutputており、(私のテストでは)ファイルの完全なリストを受け取るまでは(グロブが展開された後は)それを行いません。またoutput、入力ファイルがどれも読み取り可能でない場合は、破壊を回避します。

zshまたはでそれを書く別の方法bash

sort -u --files0-from=<(printf '%s\0' *) -o output

これは、プロセス置換を使用しています(ここで、<(...)は、パイプの読み取り側printfが書き込む先のファイルパスに置き換えられます)。その機能は、から来ているkshが、ksh拡大することに主張<(...)あなたがそれを使用することはできませんので、コマンドに別の引数を--option=<(...)構文。ただし、この構文で機能します。

sort -u --files0-from <(printf '%s\0' *) -o output

cat改行文字で終わっていないファイルがある場合に、ファイルの出力をフィードするアプローチとは異なることに注意してください。

$ printf a > a
$ printf b > b
$ printf '%s\0' a b | sort -u --files0-from=-
a
b
$ printf '%s\0' a b | xargs -r0 cat | sort -u
ab

sortは、ロケール(strcollate())の照合アルゴリズムを使用してソートし、sort -uバイトレベルの一意の行ではなく、そのアルゴリズムで同じようにソートする各行セットの1つを報告することにも注意してください。行がバイトレベルで一意であることにのみ関心があり、並べ替えられている順序にそれほど関心がない場合は、並べ替えがバイト値に基づいているCにロケールを修正することができます(memcmp();これはおそらく高速になるでしょう)大幅にアップ):

printf '%s\0' * | LC_ALL=C sort -u --files0-from=- -o output

書くのがより自然に感じられ、これはsortメモリ消費を最適化する機会も与えます。でもprintf '%s\0' *、タイプするのは少し複雑だと思います。
マラット

find . -type f -maxdepth 1 -print0代わりに使用することもできますがprintf '%s\0' *、タイプする方が簡単だとは言えません。もちろん、後者の方がエイリアスとして定義する方が簡単です。
Toby Speight 2017年

@TobySpeightにechoはがある-nので、printf -0 %sこのようなものを優先したほうがいいでしょう。低レベルは少し低いようです'%s\0'
マラ

@Toby、-maxdepthおよび-print0GNU拡張機能です(最近は広くサポートされています)。他findのsを使用すると(GNUソートを使用している場合でも、GNU findを使用する可能性があります)、LC_ALL=C find . ! -name . -prune -type f ! -name '.*' -exec printf '%s\0' {} +(GNUを使用しても、LC_ALL=C無効な文字を含む隠しファイルを除外するために)実行できますが、find一般的には少しやりすぎです持っているprintf組み込み。
ステファンChazelas

2
@malat、いつでも定義できprint0としての機能をprint0() { [ "$#" -eq 0 ] || printf '%s\0' "$@";}、次にprint0 * | sort...
ステファンChazelas

11

printfは組み込みなので、少なくともBashで機能する単純な修正であり、コマンドライン引数の制限は適用されません。

printf "%s\0" * | xargs -0 cat | sort -u > /tmp/bla.txt

echo * | xargs空白などのファイル名の処理を除いて機能します)


これはcat、すべてのファイルに対して個別のプロセスを生成する必要がないため、受け入れられたものよりも良い答えのようです。
LarsH

4
@ LarsH、find -exec {} +1回の実行で複数のファイルをまとめます。これfind -exec \;で、ファイルごとに1匹の猫になります。
ilkkachu

ああ、知っておくと良い。(パディング)
LarsH

9
find . -maxdepth 1 -type f ! -name ".*" -exec cat {} + | sort -u -o /path/to/sorted.txt

これにより、現在のディレクトリにある非表示でない通常のファイルがすべて連結され、それらの結合された内容が(重複する行を削除しながら)ファイルにソートされます/path/to/sorted.txt


大量のメモリを消費しないように、一度に2つのファイルのみを使用しようとしました(ファイル数がかなり多い)。|メモリ使用量を制限するために操作を適切にチェーンすると思いますか?
2017年

2
@malat sortは、メモリ要件で必要な場合、コア外ソートを実行します。パイプラインの左側は、比較的メモリをほとんど消費しません。
クサラナンダ

1

効率は相対的な用語であるため、最小化する要素を実際に指定する必要があります。CPU、メモリ、ディスク、時間など。議論のために、メモリ使用量を最小限に抑えたいと考えており、それを実現するためにCPUサイクルをより多く費やす用意があると仮定します。StéphaneChazelasによって提供されるようなソリューションはうまく機能します

sort -u --files0-from <(printf '%s\0' *) > ../output

ただし、個々のテキストファイルには、最初から高度な一意性があると想定しています。そうでない場合、つまり後の場合

sort -u < sample.txt > sample.srt

sample.srtは、sample.txtよりも10%以上小さいため、マージする前にファイル内の重複を削除することで、メモリを大幅に節約できます。また、コマンドをチェーンしないことで、さらに多くのメモリを節約できます。つまり、異なるプロセスの結果を同時にメモリに保存する必要はありません。

find /somedir -maxdepth 1 type f -exec sort -u -o {} {} \;
sort -u --files0-from <(printf '%s\0' *) > ../output

1
メモリ使用量がしきい値(通常は比較的小さい)を超えたときに一時ファイルを使用する手段sortとして、メモリ使用量が問題になることはほとんどありませんsortbase64 /dev/urandom | sort -uあなたのディスクをいっぱいにしますが、多くのメモリを使用しません。
ステファンChazelas

まあ、少なくともそれsortは1972年のUnix v3の元の実装を含むほとんどの実装の場合ですが、明らかにそうではありませんbusybox sort。おそらく、これは永続的なストレージを持たない小さなシステムで実行することを目的としているためです。
ステファンChazelas

yes | sort -u(すべての複製データ)は、diskはもちろん、数バイトを超えるメモリを使用する必要がないことに注意してください。しかし、sort少なくともGNUとSolaris では、2バイトの大きなファイルを大量に/tmpy\n数メガバイトの入力ごとに)書き込むので、最終的にはディスクがいっぱいになります。
ステファンChazelas

0

@ilkkachuと同様ですが、cat(1)は不要です。

printf "%s\0" * | xargs -0 sort -u

また、データが非常に長い場合は、sort(1)オプション--parallel = Nを使用することもできます。

ときNは、あなたのコンピュータを持っているCPUの数です

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.