web-dev-qa-db-ja.com

あるファイルの中で他のファイルの中にない行を見つける最も速い方法は?

私は2つの大きなファイル(ファイル名のセット)を持っています。各ファイルに約30.000行あります。私はfile2に存在しないfile1の行を見つけるための速い方法を見つけようとしています。

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

line1
line2
line3

そしてこれがfile2です。

line1
line4
line5

それから私の結果/出力は次のようになります。

line2
line3

これは動作します:

grep -v -f file2 file1

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

Diff()を使用してこれを行うには良い方法があると思いますが、出力はjust行になるはずです。

Bashや基本的なlinuxバイナリを使って、これを早く行う方法を見つけるのを手伝ってくれる人はいますか?

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

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

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

181
Niels2000

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

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

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

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

上記の new 変更されていない行は抑制されるので、変更された(つまり削除された行)あなたの場合)が出力されます。大文字と小文字の区別を無視するための-iや、厳密性の低いマッチングのためのさまざまな空白オプション(-E-b-vなど)など、他のソリューションでは提供できないdiffオプションも使用できます。


説明

オプション--new-line-format--old-line-format、および--unchanged-line-formatを使用すると、diffフォーマット指定子と同様に、printfによる相違のフォーマット方法を制御できます。これらのオプションはそれぞれ new (追加)、 old (削除)、変更なし行の形式です。空の ""に設定すると、その種の行は出力されません。

unified diff 形式に慣れている場合は、以下のように部分的に再作成できます。

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

%L指定子は問題の行です。diff -uのように、それぞれ "+" " - "または ""を頭に付けます(各グループ化された変更の先頭に---+++および@@の行がないことに注意) 。 各行に番号を付ける のような他の便利なことを%dnで行うためにこれを使うこともできます。


diffメソッド(および他の提案commおよびjoin)では、 sorted inputを指定した場合にのみ期待される出力が生成されますが、<(sort ...)を使用することもできます。その場で並べ替えます。これは簡単なawk(nawk)スクリプト(Konsolebox's answerのリンク先のスクリプトに触発されたスクリプト)で、任意の順番の入力ファイルを受け入れます。およびそれらは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の内容全体を行番号付き索引配列ll1[]に1行ずつ、file2の内容全体を行番号付き連想配列ss2[]に1行ずつ格納します。両方のファイルが読み取られたら、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つの配列に格納します。一方は行番号ll1[]、もう一方は行内容ss1[]でインデックスされています。その後、file2が読み込まれると、一致する各行がll1[]およびss1[]から削除されます。最後に、file1の残りの行が出力され、元の順序が維持されます。

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

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

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

非GNUシステムのユーザのために、ほぼ確実に入手できるGNU coreutilsパッケージがあります。OSX上で Apple Xcode toolsの一部として含まれています。 GNU diffawk、GNUバージョンではなくPOSIX/BSD splitのみです。

179
mr.spuratic

comm コマンド( "common"の略)が役に立つかもしれません_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ファイルは、実際にはかなり読みやすいです。

.

184
JnBrymn

Konsoleboxが提案したように、ポスターはソリューションをgrepします

grep -v -f file2 file1

単に-Fオプションを追加して、パターンを正規表現ではなく固定文字列として扱う場合、実際には非常に高速に動作します。私はこれを私が比較しなければならなかった〜1000行のファイルリストのペアで検証しました。 -Fを使用すると、grep出力をwc -lにリダイレクトするときに0.031秒(実数)かかりましたが、それ以外の場合は2.278秒(実数)かかりました。

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

そのため、入力をソートする必要がなく、高速で柔軟性があり(大文字と小文字の区別など)、さらに(私が考える)あらゆるPOSIXシステムで機能する解決策は、次のとおりです。

grep -F -x -v -f file2 file1
19
pbz

並べ替えや差分としての速度は何ですか?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted
10
Puggan Se

あなたが「派手な道具」に欠けているなら、例えばいくつかの最小限のLinuxディストリビューションでは、catsortおよびuniqだけの解決策があります。

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と比べて比較的速いです。

6
Ondra Žižka
$ join -v 1 -t '' file1 file2
line2
line3

-tは、いくつかの行にスペースがある場合、それが行全体を比較することを確認します。

5
Steven Penny

あなたは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())
'
2
HelloGoodbye

Fgrepを使用するか、grepに-Fオプションを追加すると解決する場合があります。しかし、より速い計算のためにはAwkを使うことができます。

あなたはこれらのAwkメソッドのうちの1つを試すことができます:

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

2
konsolebox

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

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

1
BAustin