別のファイルにない1つのファイル内の行を見つける高速な方法?


241

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/^>\ //'

確かに、もっと良い方法があるに違いない?


1
それが高速である場合は、これを試みることができる:awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
ケント


4
grep -v -f file2 file1について教えてくれてありがとう
Rahul Prasad


ツールセットを削減した簡単な方法:cat file1 file2 file2 | sort | uniq --unique、以下の私の答えを参照してください。
OndraŽižka18年

回答:


233

これは、GNU diff出力のold / new / unchanged行のフォーマットを制御することで実現できます。

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

これを機能させるに、入力ファイルをソートする必要があります。bash(と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方法を制御diffでき printfます。これらのオプションは、それぞれ新しい(追加された)、古い(削除された)、および変更されていない行をフォーマットします。1を空の ""に設定すると、そのような行の出力が防止されます。

統一されたdiff形式に慣れている場合は、次のようにして部分的に再作成できます。

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

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


このdiffメソッドは、他の提案commjoinとともに、ソートされた入力で期待される出力のみを生成しますが<(sort ...)、所定の位置でソートするために使用できます。ここでは簡単ですawk(nawkの)スクリプト(スクリプトに触発さは、リンク先のKonsoleboxの答えで)任意の入力ファイルを命じ受け入れる、彼らはFILE1に発生順に不足している行を出力します。

# 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のコンテンツ全体を行番号のインデックス配列に1行ずつ格納しll1[]、file2のコンテンツ全体を行コンテンツのインデックス付き連想配列に1行ずつ格納しss2[]ます。両方のファイルが読み取られた後、反復しll1in演算子を使用し、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

使用と配置に注意してください-という意味stdingawk、コマンドラインを。これはsplitfile1から、呼び出しごとに20000行のチャンクで提供されます。

非GNUシステム上のユーザーの場合、ほぼ確実に存在しているのGNU coreutilsのがの一部としてOSXに含め、あなたが得ることができ、パッケージのApple Xcodeの GNUツールを提供しdiffawkけれども唯一のPOSIX / BSD splitではなくGNUバージョン。


1
これは、巨大なgrepにかかる時間のごく一部で、私が必要とするものを正確に実行します。ありがとう!
Niels2000 2013


私たちの一部はgnuを利用していません[OS X bsd here ...] :)
rogerdpack

1
私はあなたが意味することを仮定しますdiff:一般的に入力ファイルは異なりますdiff、その場合1が返されます。ボーナスと見なしてください;-)シェルスクリプトでテストしている場合、0と1は予期される終了コードであり、2は問題を示します。
mr.spuratic

1
@ mr.spuraticああそう、今私はそれをで見つけますman diff。ありがとう!
Archeosudoerus

242

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ファイルは実際には非常に読みやすいです。


6
OSXで問題なく動作します。
pisaruk

40
ソートされた入力の要件はおそらく強調されるべきです。
tripleee 2017

20
comm入力がソートされていることを確認するオプションもあります--check-order(とにかくそうですが、このオプションを使用すると、続行する代わりにエラーが発生します)。:しかし、ファイルを並べ替えるだけで行うcom -23 <(sort file1) <(sort file2)というように
マイケル

Windowsで生成されたファイルとLinuxで生成されたファイルを比較してcommいましたが、まったく機能していないようです。それが行末に関するものであることを理解するのにしばらく時間がかかりました。同じように見える行でも、行末が異なる場合は異なると見なされます。このコマンドdos2unixを使用して、CRLF行末をLFにのみ変換できます。
ZeroOne

23

konsoleboxが提案したように、ポスターgrepソリューション

grep -v -f file2 file1

-Fオプションを追加するだけで、パターンは正規表現ではなく固定文字列として扱われるため、実際には(高速に)うまく機能します。比較する必要があった〜1000行のファイルリストのペアでこれを確認しました。と-Fにgrepの出力をリダイレクトするとき、それは、2.278秒(実)を取ったことなく、一方でそれは、0.031秒(実)を取りましたwc -l

これらのテストには-xスイッチも含まれています。これは、file2にfile1の1つ以上の行のすべてではなく一部に一致する行が含まれている場合に完全に正確にするために必要なソリューションの一部です。

したがって、入力をソートする必要がなく、高速で柔軟なソリューション(大文字と小文字の区別など)は次のとおりです。

grep -F -x -v -f file2 file1

これはgrepのすべてのバージョンでは機能しません。たとえば、macOSで失敗します。ファイル1の行が、ファイル2に存在しないものとして表示されますが、その部分文字列である別の行と一致する場合。または、このソリューションを使用するために、macOSGNU grepをインストールできます。


ええ、それは機能しますが、-Fこれでもうまくスケーリングしません。
Molomby 2016

これはそれほど速くはありません
。50

実際に、この方法は、それゆえunsortingによってダウンドラッグこの1つはソートされていないファイルを扱うことができるので、まだ遅くCOMMの方法よりもされ、COMMは、ソートを利用しています
workplaylifecycle

@workplaylifecycle並べ替えの時間を追加する必要がありfile2ます。これは、非常に大きい場合のボトルネックになる可能性があります。
rwst

ただし、-xオプションを指定したgrepは明らかにより多くのメモリを使用します。file26-10の収容180M言葉私のプロセスが得たバイトKilled... 32ギガバイトのRAMのマシン上で
RWST

11

ソートと差分の速度はどうですか?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted

1
diffを実行する前にファイルをソートする必要があることを思い出してくれてありがとう。sort + diffははるかに高速です。
Niels2000 2013

4
one liner ;-) diff <(sort file1 -u)<(sort file2 -u)
steveinatorx

11

あなたには、いくつかの最低限のLinuxディストリビューションでは例えば「空想ツール」、の短いなら、そこだけで解決策はあるcatsortuniq

cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

テスト:

seq 1 1 7 | sort --random-sort > includes.txt
seq 3 1 9 | sort --random-sort > excludes.txt
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

# Output:
1
2    

これは、と比べても比較的高速grepです。


1
注-一部の実装は--uniqueオプションを認識しません。あなたは使用することができるはず標準POSIXオプションを、このために:| uniq -u
AndrewF

1
例では、「2」はどこから来たのですか?
Niels2000

1
@ Niels2000は、seq 1 1 71から7まで、1から1までの数を作成します。
Eirik Lygre

5
$ join -v 1 -t '' file1 file2
line2
line3

-tあなたは線の一部のスペースを持っていた場合には、行全体を比較していることを確認します。


のようにcommjoin結合操作を実行しているフィールドで両方の入力行をソートする必要があります。
tripleee 2017

4

Pythonを使用できます。

python -c '
lines_to_remove = set()
with open("file2", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("f1", "r") as f:
    for line in f.readlines():
        if line.strip() not in lines_to_remove:
            print(line.strip())
'

4

使用combineからmoreutilsサポートしていること、パッケージ、セットユーティリティnotandorxor操作

combine file1 not file2

つまり、file1にはあるがfile2にはない行を取得します

または、file1の行からfile2の行を差し引いて

注:combine、操作を実行する前に両方のファイルで一意の行を並べ替えて検索しますが、実行diffしません。したがって、diffとの出力に違いがある場合がありcombineます。

だから実質的にあなたは言っている

file1とfile2で異なる行を見つけて、file1の行からfile2の行を差し引いてください

私の経験では、他のオプションよりもはるかに高速です


2

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

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

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


2
+1これは、入力をソートする必要がない唯一の回答です。OPはその要件に満足しているようですが、多くの現実のシナリオでは受け入れられない制約です。
tripleee 2014年

1

私がこれを通常行う方法は--suppress-common-linesフラグを使用することですが、これはサイドバイサイド形式で行う場合にのみ機能することに注意してください。

diff -y --suppress-common-lines file1.txt file2.txt


0

私にとっては、通常のifおよびforループステートメントを使用すると完全に機能することがわかりました。

for i in $(cat file2);do if [ $(grep -i $i file1) ];then echo "$i found" >>Matching_lines.txt;else echo "$i missing" >>missing_lines.txt ;fi;done

2
DontReadLinesWithForを参照してください。また、grep結果のいずれかが複数の単語に展開される場合、またはfile2エントリのいずれかがシェルによってグロブとして扱われる可能性がある場合、このコードは非常に悪い動作をします。
Charles Duffy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.