私は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/^>\ //'
確かに、もっと良い方法があるはずですか?
これを達成するには、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 diff
、awk
、GNUバージョンではなくPOSIX/BSD split
のみです。
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
ファイルは、実際にはかなり読みやすいです。
.
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
並べ替えや差分としての速度は何ですか?
sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted
あなたが「派手な道具」に欠けているなら、例えばいくつかの最小限のLinuxディストリビューションでは、cat
、sort
および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
と比べて比較的速いです。
$ join -v 1 -t '' file1 file2
line2
line3
-t
は、いくつかの行にスペースがある場合、それが行全体を比較することを確認します。
あなたは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())
'
Fgrepを使用するか、grepに-Fオプションを追加すると解決する場合があります。しかし、より速い計算のためにはAwkを使うことができます。
あなたはこれらのAwkメソッドのうちの1つを試すことができます:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
私が通常これを行う方法は--suppress-common-lines
フラグを使用することですが、これはあなたがサイドバイサイドフォーマットでそれを行う場合にのみ機能することに注意してください。
diff -y --suppress-common-lines file1.txt file2.txt