別のファイルにある1つのファイルから行を削除する


126

ファイルがありますf1

line1
line2
line3
line4
..
..

別のファイルにあるすべての行を削除したいf2

line2
line8
..
..

とを使って何かを試しましたがcatsed意図したものにさえ近づきませんでした。これどうやってするの?



別のファイルの文字列を「含む」場合でもファイルから行を削除する場合(たとえば、部分一致)、unix.stackexchange.com
questions /

回答:


154

grep -v -x -f f2 f1 トリックを行う必要があります。

説明:

  • -v 一致しない行を選択するには
  • -x 行全体のみに一致する
  • -f f2 パターンを取得する f2

一つは、代わりに使用することができるgrep -Ffgrepと一致するように、固定文字列からf2ではなくパターンを(場合には、あなたが「あなたが何を得るかどうかを確認」方式ではなく、中の線の治療に行削除したいf2正規表現パターンとを)。


22
これはO(n²)の複雑さで、ファイルに数K行を超える行が含まれるようになると、完了するまでに数時間かかります。
Arnaud Le Blanc

11
どのSOが提案したアルゴリズムがO(n ^ 2)の複雑さを持っているかを把握することは、O(n)の複雑さしかありませんが、それでも競合するのに数時間かかることがあります。
HDave

2
私はこれをそれぞれ〜2k行の2つのファイルで試してみましたが、OSによって強制終了されました(確かに、これはそれほど強力ではないVMですが)。
Trebor Rude、

1
私はこれの優雅さが大好きです。私は、Jona Christopher Sahnwalの答えの速さを好みます。
アレックスホール

1
@ arnaud576875:よろしいですか?それはの実装に依存しgrepます。f2検索を開始する前に適切に前処理を行うと、検索にはO(n)時間しかかかりません。
HelloGoodbye 2017

56

代わりにcommを試してください(f1とf2が「ソート済み」であると想定)

comm -2 -3 f1 f2

5
commソリューションが質問f1を使用しての前提条件である行が並べ替えられていることを示していないという質問があるかどうかcomm
わかり

1
私のファイルはソートされており、片方に250,000以上の行があり、もう一方には28,000行しかなかったので、これは私にとってはうまくいきました。ありがとう!

1
これが機能する場合(入力ファイルがソートされる場合)、これは非常に高速です!
マイクジャービス

arnaud576875のソリューションと同様に、cygwinを使用している私にとっては、2番目のファイルの保持したい重複行が排除されました。
アレックスホール

8
もちろん、最初のファイルをソートするために、プロセス置換を使用することができますcomm -2 -3 <(sort f1) <(sort f2)
davemyron

14

大きすぎない除外ファイルの場合は、AWKの連想配列を使用できます。

awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt 

出力は、「from-this.txt」ファイルと同じ順序になります。このtolower()関数は、必要に応じて大文字と小文字を区別しません。

アルゴリズムの複雑さは、おそらくO(n)(exclude-these.txt size)+ O(n)(from-this.txt size)になります。


それほど大きくないファイルを言うのはなぜですか?ここでの恐怖は(私が思うに)awkがハッシュを作成するためにシステムメモリを使い果たしているのでしょうか、それとも他の制限がありますか?
rogerdpack 2015年

フォロワーには、行を「サニタイズ」する他のより積極的なオプションがあります(連想配列を使用するには比較を正確に行う必要があるため)。例:unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack:大きな除外ファイルには大きなハッシュ配列が必要になります(処理時間が長くなります)。大きな "from-this.txt"は、長い処理時間のみを必要とします。
追って通知があるまで一時停止。

1
exclude-these.txt空の場合、これは失敗します(つまり、出力は生成されません)。以下の@ jona-christopher-sahnwaldtの回答は、この場合に有効です。複数のファイルを指定することもできます。例awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell

11

デニスウィリアムソンの回答に似ています(主に構文の変更、たとえばNR == FNRトリックではなく明示的にファイル番号を設定する):

awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt

アクセスr[$0]すると、その行のエントリが作成されます。値を設定する必要はありません。

awkが一定のルックアップと(平均して)一定の更新時間を持つハッシュテーブルを使用すると仮定すると、この時間の複雑さはO(n + m)になります。ここで、nとmはファイルの長さです。私の場合、nは〜2500万、mは〜14000でした。awkソリューションはソートよりもはるかに高速で、元の順序を維持することも好みました。


これはデニスウィリアムソンの回答とどう違うのですか?ハッシュへの代入を行わない唯一の違いは、これよりわずかに速いですか?アルゴリズムの複雑さは彼と同じですか?
rogerdpack、2015年

違いは主に構文です。変数はfよりも明確ですがNR == FNR、それは好みの問題です。ハッシュへの割り当ては非常に高速であるため、2つのバージョン間で測定可能な速度の違いはありません。私は複雑さについて間違っていたと思います-ルックアップが一定の場合、更新も一定のはずです(平均して)。更新が対数になると思った理由がわかりません。回答を編集します。
jcsahnwaldtがモニカを復活させます2015

私はこれらの答えをたくさん試しましたが、これはAMAZEBALLSの速さでした。数十万行のファイルがありました。魅力のように働いた!
T氏T

1
これが私の推奨する解決策です。複数のファイルで動作し、空の除外ファイルも使用できますawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out。一方、他のawkソリューションは空の除外ファイルで失敗し、1つしか取得できません。
グラハムラッセル

5

Ruby(1.9以降)を使用している場合

#!/usr/bin/env ruby 
b=File.read("file2").split
open("file1").each do |x|
  x.chomp!
  puts x if !b.include?(x)
end

これはO(N ^ 2)の複雑さです。パフォーマンスを気にしたい場合は、ここに別のバージョンがあります

b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}

ハッシュを使用して減算を行うので、複雑さO(n)(aのサイズ)+ O(n)(bのサイズ)も同様です。

これはuser576875の好意による少しのベンチマークですが、上記の100K行です:

$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1
$ for i in $(seq 1 2 100000); do echo "$i"; done|sort --random-sort > file2
$ time ruby test.rb > ruby.test

real    0m0.639s
user    0m0.554s
sys     0m0.021s

$time sort file1 file2|uniq -u  > sort.test

real    0m2.311s
user    0m1.959s
sys     0m0.040s

$ diff <(sort -n ruby.test) <(sort -n sort.test)
$

diff 生成された2つのファイルの間に違いがないことを示すために使用されました。


1
これはO(n²)の複雑さで、ファイルに数K行を超える行が含まれるようになると、完了するまでに数時間かかります。
Arnaud Le Blanc、

彼が大きなファイルについて言及しなかったので、私はこの時点で本当に気にしません。
くるみ

3
それほど防御的である必要はありません。@ user576875があなたの回答や何かに反対票を投じたわけではありません。:-)
ジョンパーカー

とても素敵な2番目のバージョン、ルビーが勝ちます:)
Arnaud Le Blanc

4

他のさまざまな答えの間のいくつかのタイミング比較:

$ for n in {1..10000}; do echo $RANDOM; done > f1
$ for n in {1..10000}; do echo $RANDOM; done > f2
$ time comm -23 <(sort f1) <(sort f2) > /dev/null

real    0m0.019s
user    0m0.023s
sys     0m0.012s
$ time ruby -e 'puts File.readlines("f1") - File.readlines("f2")' > /dev/null

real    0m0.026s
user    0m0.018s
sys     0m0.007s
$ time grep -xvf f2 f1 > /dev/null

real    0m43.197s
user    0m43.155s
sys     0m0.040s

sort f1 f2 | uniq -u どちらのファイルにも複数回出現する行が削除されるため、対称的な違いすらありません。

commはstdinおよびhere文字列でも使用できます。

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a

2

SQLiteシェルに適したジョブのようです:

create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);
-- comment: if you have | in your files then specify  .separator ××any_improbable_string×× 
.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in (select line from file1);
.q

1

これをsedで試しましたか?

sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh

sed -i 's#$#%%g'"'"' f1#g' f2.sh

sed -i '1i#!/bin/bash' f2.sh

sh f2.sh

0

「プログラミング」の答えではありませんが、ここにすばやく簡単な解決策があります。http://www.listdiff.com/compare-2-lists-difference-toolにアクセスしてください

明らかに巨大なファイルでは機能しませんが、私にとってはうまくいきました。いくつかのメモ:

  • 私はどのような方法でもWebサイトとは関係ありません(それでも信じられない場合は、オンラインで別のツールを検索できます。検索語句「オンラインで差分リストを設定」を使用しました)
  • リンクされたWebサイトはすべてのリスト比較でネットワーク呼び出しを行うようですので、機密データをフィードしないでください
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.