322

2 つの大きなファイル (ファイル名のセット) があります。各ファイルに約 30.000 行。file2 に存在しない file1 の行をすばやく見つける方法を見つけようとしています。

たとえば、これがfile1 の場合:

line1
line2
line3

そして、これはfile2です:

line1
line4
line5

次に、私の結果/出力は次のようになります。

line2
line3

これは機能します:

grep -v -f file2 file1

しかし、私の大きなファイルで使用すると、非常に遅くなります。

を使用してこれを行う良い方法があると思いますが、出力は行だけdiff()で、他には何もないはずであり、そのためのスイッチが見つからないようです。

bash と基本的な Linux バイナリを使用して、これを行うための迅速な方法を見つけるのを手伝ってくれる人はいますか?

編集:私自身の質問をフォローアップするために、これは私がこれまでに見つけた最良の方法ですdiff()

 diff file2 file1 | grep '^>' | sed 's/^>\ //'

確かに、もっと良い方法があるはずですか?

4

12 に答える 12

333

commコマンド (「共通」の略) が役立つ場合がありますcomm - compare two sorted files line by line

#find lines only in file1
comm -23 file1 file2 

#find lines only in file2
comm -13 file1 file2 

#find lines common to both files
comm -12 file1 file2 

このmanファイルは、実際には非常に読みやすいです。

于 2014-10-28T21:46:19.093 に答える
285

diffこれは、GNU出力の古い/新しい/変更されていない行のフォーマットを制御することで実現できます。

diff --new-line-format="" --unchanged-line-format=""  file1 file2

これを機能させるには、入力ファイルをソートする必要があります。bash(and ) を使用zshすると、プロセス置換でその場でソートできます<( )

diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)

上記の新しい行と変更されていない行は抑制されているため、変更された行 (つまり、この場合は削除された行) のみが出力されます。diff大文字と小文字を無視するなど、他のソリューションでは提供されないいくつかのオプションを使用することも、厳密でない一致のために-iさまざまな空白オプション ( -E、など) を使用することもできます。-b-v


説明

オプション--new-line-format--old-line-formatを使用すると、フォーマット指定子と同様に、違いをフォーマット --unchanged-line-formatする方法を制御できます。これらのオプションは、新しい(追加された)、古い(削除された) 行、および変更されていない行をそれぞれフォーマットします。1 つを空の "" に設定すると、そのような行の出力が防止されます。diffprintf

統一された差分形式に精通している場合は、次の方法で部分的に再作成できます。

diff --old-line-format="-%L" --unchanged-line-format=" %L" \
     --new-line-format="+%L" file1 file2

指定子は問題の%L行であり、それぞれに「+」「-」または「」のような接頭辞を付けます(違いを出力するだけで、グループ化された各変更の上部にand行diff -u がないことに注意してください)。これを使用して、各行に で番号を付けるなど、他の便利なことを行うこともできます。--- +++@@%dn


メソッドはdiff(他の提案commおよびとともに)ソートされたjoin入力で期待される出力のみを生成しますが、その場でソートするために使用できます。これは、任意に順序付けられた入力ファイルを受け入れ、欠落している行を file1 で発生する順序で出力する単純な(nawk) スクリプト (Konsolebox の回答にリンクされているスクリプトに触発されたもの)です。<(sort ...)awk

# output lines in file1 that are not in file2
BEGIN { FS="" }                         # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; }     # file1, index by lineno
(NR!=FNR) { ss2[$0]++; }                # file2, index by string
END {
    for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}

これは、file1 の内容全体を行ごとに行番号のインデックス付き配列ll1[]に格納し、file2 の内容全体を行ごとにインデックス付き連想配列に格納しss2[]ます。両方のファイルが読み取られた後、反復しll1て演算子を使用してin、file1 の行が file2 に存在するかどうかを判断します。diff(重複がある場合、メソッドへの出力が異なります。)

ファイルが十分に大きく、両方を格納するとメモリの問題が発生する場合は、file1 のみを格納し、file2 が読み取られる途中で一致を削除することにより、CPU をメモリと交換できます。

BEGIN { FS="" }
(NR==FNR) {  # file1, index by lineno and string
  ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) {  # file2
  if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
  for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}

上記は file1 の内容全体を 2 つの配列に格納します。1 つは行番号ll1[]でインデックス付けされ、もう 1 つは行コンテンツでインデックス付けされますss1[]。次に、file2 が読み取られると、一致する各行が および から削除されll1[]ますss1[]。最後に、file1 の残りの行が出力され、元の順序が維持されます。

この場合、前述の問題により、GNU (フィルタリングは GNU 拡張機能です) を使用して分割して征服splitし、file1 のチャンクを使用して繰り返し実行し、毎回 file2 を完全に読み取ることもできます。

split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1

コマンドラインでの-意味stdinの使用と配置に注意してください。gawkこれは、split呼び出しごとに 20000 行のチャンクで from file1 によって提供されます。

GNU 以外のシステムのユーザーの場合、GNU バージョンではなくPOSIX/BSD のみですが、 GNUを提供するApple Xcodeツールの一部として OSX を含め、ほぼ確実に入手できる GNU coreutils パッケージがあります。diffawksplit

于 2013-08-13T09:24:51.190 に答える
11

sort と diff の速度は?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted
于 2013-08-13T09:12:44.327 に答える
2

fgrep を使用するか、grep に -F オプションを追加すると役立つ場合があります。ただし、計算を高速化するには、Awk を使用できます。

次の Awk メソッドのいずれかを試すことができます。

http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219

于 2013-08-13T09:17:44.040 に答える