web-dev-qa-db-ja.com

2セット/ファイル間の一意の値を抽出する

Linux/Shell envで作業して、次のことをどのように達成できますか?

テキストファイル1に含まれるもの:

1
2
3
4
5

テキストファイル2に含まれるもの:

6
7
1
2
3
4

ファイル1にはないファイル2のエントリを抽出する必要があります。この例では、「6」と「7」です。

コマンドラインからこれを行うにはどうすればよいですか?

どうもありがとう!

24
mark
$ awk 'FNR==NR {a[$0]++; next} !a[$0]' file1 file2
6
7

コードの仕組みの説明:

  • File1で作業している場合は、表示されるテキストの各行を追跡します。
  • File2で作業していて、行テキストが表示されていない場合は、印刷します。

詳細の説明:

  • FNRは現在のファイルのレコード番号です
  • NRは、すべての入力ファイルの現在の全体的なレコード番号です
  • FNR==NRは、file1を読み取っているときにのみtrueです
  • $0は現在のテキスト行です
  • a[$0]は、現在のテキスト行にキーが設定されたハッシュです
  • a[$0]++現在のテキスト行を見たトラック
  • !a[$0]は、行テキストが表示されていない場合にのみtrueです
  • 上記のパターンがtrueを返した場合、テキスト行を出力します。これは、明示的なアクションが指定されていない場合のデフォルトのawk動作です
54
SiegeX

あまり知られていないユーティリティを使用する:

sort file1 > file1.sorted
sort file2 > file2.sorted
comm -1 -3 file1.sorted file2.sorted

これは重複を出力するので、1がある場合は3 in file1、ただし2でfile2、これでも1を出力します3。これが適切でない場合は、sortからuniqまでの出力をパイプしてから、ファイルに書き込みます。

sort file1 | uniq > file1.sorted
sort file2 | uniq > file2.sorted
comm -1 -3 file1.sorted file2.sorted

GNU coreutilsパッケージには、あらゆる種類のテキスト操作を可能にする多くのユーティリティがあります。

16

次の解決策のどれが「大きい」ファイルの「最速」だったのかと思っていました。

awk 'FNR==NR{a[$0]++}FNR!=NR && !a[$0]{print}' file1 file2 # awk1 by SiegeX
awk 'FNR==NR{a[$0]++;next}!($0 in a)' file1 file2          # awk2 by ghostdog74
comm -13 <(sort file1) <(sort file2)
join -v 2 <(sort file1) <(sort file2)
grep -v -F -x -f file1 file2

要するに私のベンチマークの結果:

  • grep -Fxfは使用しないでください。非常に遅くなります(テストでは2〜4回)。
  • commjoinよりもわずかに高速です。
  • File1とfile2がすでにソートされている場合、commjoinはawk1 + awk2よりもはるかに高速です。 (もちろん、それらはソートされたファイルを想定していません。)
  • awk1 + awk2は、おそらくより多くのRAMとより少ないCPUを使用します。commの実際の実行時間は、より多くのスレッドを使用するという事実が原因であると考えられます。CPU時間はawk1 + awk2。

簡潔にするため、詳細は省略します。ただし、興味のある方は私に連絡したり、テストを繰り返したりできると思います。おおよその設定は

# Debian Squeeze, Bash 4.1.5, LC_ALL=C, slow 4 core CPU
$ wc file1 file2
  321599   321599  8098710 file1
  321603   321603  8098794 file2

最速の実行の典型的な結果

awk2: real 0m1.145s  user 0m1.088s  sys 0m0.056s  user+sys 1.144
awk1: real 0m1.369s  user 0m1.324s  sys 0m0.044s  user+sys 1.368
comm: real 0m0.980s  user 0m1.608s  sys 0m0.184s  user+sys 1.792
join: real 0m1.080s  user 0m1.756s  sys 0m0.140s  user+sys 1.896
grep: real 0m4.005s  user 0m3.844s  sys 0m0.160s  user+sys 4.004

ところで、awkiesの場合:a[$0]=1a[$0]++よりも高速で、(!($0 in a))(!a[$0])よりも高速であるようです。したがって、awkソリューションについては、次のことをお勧めします。

awk 'FNR==NR{a[$0]=1;next}!($0 in a)' file1 file2
8
xebeche

grep:

grep -F -x -v -f file_1 file_2 
5
sid_com

どうですか:

diff file_1 file_2 | grep '^>' | cut -c 3-

これにより、file_1にないエントリがfile_2に出力されます。反対の結果を得るには、 '>'を '<'に置き換える必要があります。 'cut'は、 'diff'によって追加された、元のコンテンツの一部ではない最初の2文字を削除します。

ファイルはソートする必要さえありません。

5
Ivo

ここに別のawkソリューションがあります

$ awk 'FNR==NR{a[$0]++;next}(!($0 in a))' file1 file2
6
7
3
ghostdog74

コマンドラインからこれを行うことにreallyを設定している場合、 this site (「重複は見つかりませんでした」を検索)には重複を検索するawkの例。それを見るのは良い出発点かもしれません。

ただし、PerlまたはPython=を使用することをお勧めします。基本的に、プログラムのフローは次のようになります。

findUniqueValues(file1, file2){
    contents1 = array of values from file1
    contents2 = array of values from file2
    foreach(value2 in contents2){
        found=false
        foreach(value1 in contents1){
            if (value2 == value1) found=true
        }
        if(!found) print value2
    }
}

O(n ^ 2)時間の複雑さがあるため、これはこれを行う最もエレガントな方法ではありませんが、うまく機能します。

0
David Weiser