テキストファイル内の重複行を削除する方法は?


126

私の巨大な(最大2 GiB)テキストファイルには、その中のすべての行の約100の正確な複製が含まれています(ファイルはCSVのようなデータテーブルであるため、私の場合は役に立ちません)。

私が必要なのは、元のシーケンスの順序を維持しながら、すべての繰り返しを削除することです(ただし、これはパフォーマンスを大幅に向上させるために犠牲にすることができます)。結果では、各行は一意である必要があります。等しい行が100行ある場合(通常、重複はファイル全体に広がり、隣人にはなりません)、残された種類は1つだけです。

これを実装するためのプログラムをScalaで作成しました(Scalaを知らない場合はJavaを検討してください)。しかし、おそらくこれをより高速に実行できる、より高速なCで作成されたネイティブツールがあるのでしょうか。

更新:awk '!seen[$0]++' filenameファイルが2 GiB以下に近い限り、解決策はうまく機能しているように見えましたが、8 GiBファイルをクリーンアップしようとしても機能しなくなりました。4 GiB RAMを搭載したMacと4 GiB RAMおよび6 GiBスワップを搭載した64ビットWindows 7 PCで無限に実行されているように見えます。そして、この経験を考えると、4 GiB RAMを搭載したLinuxで試してみることに熱心ではありません。


これはあなたの順序を破壊しますが、sort -uを試してみましたが、そのような巨大なファイルでどのように、または実行できるかわかりません
-0x7c0

5
Cは多くの場合Javaよりも大幅に高速ではありません。Cを(順序どおりに)実行している場合、ここで答えを得て実装し、実行を完了する前に終了する可能性がかなりあります。故障していると、sort -uおそらくより高速になります。
ケビン

回答:


215

awk#bash(Freenodeの)で見ソリューション:

awk '!seen[$0]++' filename

1
2Gファイルでこれを試したところ、ノートブックで3分かかりました。悪くない。uniq filenameも試しました| awk '!seen [$ 0] ++'ですが、それは速くありませんでした。
mgjk

これは驚くほど速く、より詳細なよりawk(ジルの答えで拡大説明として示した)2列ルックアップを使用したバージョン: 0m36.132s0m49.958s ..5000万ラインのために..私はボトルネックがI / Oだろうと思いました、しかし、余分な配列ルックアップは...配列内の100万個の要素がかなり大きな凹みを作るようです
...-Peter.O

しかし、これはsort -u ....と比較してどうですか?
ハッシュウィザード

1
@HashWizard:このコマンドはソートしませんが、同じ行の次の出現をすべて削除します
-enzotib

1
@MaxWilliamsはい、それらはランダムに分散されています。
setholopolus

47

run以外に大きなメモリを必要としない標準ユーティリティを使用した単純な(明らかなことではありません)メソッドがあります。sortほとんどの実装では、巨大なファイルに対する特定の最適化(優れた外部ソートアルゴリズム)があります。この方法の利点は、インタープリター言語内ではなく、専用のユーティリティ内のすべての行のみをループすることです。

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

すべての行が空白以外の文字で始まる場合、いくつかのオプションを省くことができます。

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

大量の複製の場合、メモリに各行の単一のコピーを保存するだけでよい方法の方がパフォーマンスが向上します。いくつかの解釈オーバーヘッドがありますが、そのための非常に簡潔なawkスクリプトがあります(すでにenzotibが投稿しています)。

<input awk '!seen[$0]++'

あまり簡潔!seen[$0] {print} {seen[$0] += 1}ではありません。つまり、まだ表示されていない場合は現在の行を出力しseen、この行のカウンターをインクリメントします(初期化されていない変数または配列要素の数値は0です)。

長い行の場合、各行のスプーフィング不可能なチェックサム(暗号ダイジェストなど)のみを保持することにより、メモリを節約できます。たとえば、SHA-1を使用する場合、必要なのは20バイトと1行あたりの一定のオーバーヘッドだけです。しかし、ダイジェストの計算はかなり遅いです。この方法は、高速なCPU(特にダイジェストを計算するためのハードウェアアクセラレータを搭載したCPU)があり、ファイルのサイズに比べて多くのメモリと十分に長い行がない場合にのみ有効です。各行のチェックサムを計算できる基本的なユーティリティはありません。Perl / Python / Ruby /の解釈オーバーヘッドを負担するか、専用のコンパイル済みプログラムを作成する必要があります。

<input perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output

@Gillesの説明に基づいて、awk '!seen[$0]++'awkが2つの重複行を見つけた場合、常に最初の行を保持し、それ以降のすべての行を無視するということですか?(または最後のものを保持しますか?)
user779159

1
@ user779159最初の行を保持します。各入力行はすぐに印刷されるか(最初の出現)、まったく印刷されません(繰り返しの出現)。
ジル

しかし、それはsort -u ...と比較してどうですか?
HashWizard

@HashWizardプレーンsort -uは順序を変更します。私の答えは、順序(正確には最初の出現の順序)を保持するソリューションを示しています。
ジル

@Gillesは、重複が50%の大きなファイル(10G)の場合、sort -uよりも高速だと言いますか?
ハッシュウィザード

25
sort -u big-csv-file.csv > duplicates-removed.csv

出力ファイルはソートされることに注意してください。


1
awk他の回答のコマンドほど高速ではありませんが、概念的には簡単です!
ヨハン

@Johann数十万(さらには百万)の短い改行で終わる文字列を含むファイルに対してこれをかなり頻繁に行っています。私が行っている実験の結果はかなり早く得られます。何度も何度も実行されるスクリプトで使用する場合、より重要になる可能性があり、時間の節約は相当なものになります。
ヴラディスラフドブガレス

1
sort -uソート後ではなく、ソート中に重複を削除するために使用します。(メモリ帯域幅を節約します)別のプログラムにパイプします)。awk出力をソートしたい場合にのみ、バージョンよりも優れています。(この質問のOPは、彼の元の順序を保持することを望んでいるので、これはわずかに異なるユースケースの良い答えです。)
ピーター

私にとっては、550万行のファイル(合計1.8 GB)に約1分かかりました。ブリリアント。
マックスウィリアムズ

18

重複排除されたファイルをメモリに保存する余裕があると仮定すると(データが実際に100倍に複製される場合、それは約20MiB +オーバーヘッドになるはずです)、Perlを使用してこれを非常に簡単に行うことができます。

$ perl -ne 'print unless $dup{$_}++;' input_file > output_file

これにより順序も保持されます。

%dup追加の無料ボーナスとして、必要に応じて、ハッシュから各行の出現回数を抽出できます。

必要に応じてawk、これも実行する必要があります(perlバージョンと同じロジック、同じ順序、dup変数に収集された同じデータ):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file

これは@Matが良すぎる、私はファイルを丸lurみしようとしていた;-)。
ニキルマーリー

今彼のsedは、あまりにも:-)魔法weaveryをawkのため@ManAtWorkを待っている
ニキルMulley

再びawkのヒントについて素晴らしい:-)
ニキル・マレー

1
perlスクリプトを変更して、重複する隣接行のみを削除することは可能ですか?
-dumbledad

2
@dumbledad:uniqそれだけですべてのことを行います
マット

3

他の回答ではインプレースサポートが提供されていないため、以下にその1つを示します。

gawk -i inplace '!a[$0]++' file

これは順序を維持しますか?ところで、これは私にはうまくいきませんでした。私のバージョンは次のとおりです。GNU Awk 4.0.2
レオニード

1
@Leonidはい、そうです。一意の行の最初の出現を印刷します。インプレースのサポートは、最初の2013年にリリースされたバージョン4.1で導入されました
rindeal -ヤンChren

3

http://www.computerhope.com/unix/uuniq.htmを使用できuniq ます

uniq ファイル内の繰り返される行を報告または除外します。


答えを出すとき、あなたの答えなぜなのかについての説明をすることが望ましいです。それで、この答えは以前のいくつかの答えとどう違うのですか?
スティーブンラウフ

1
uniqのmanページから:注: 'uniq' does not detect repeated lines unless they are adjacent. したがって、最初にソートし、重複しない行の順序を緩める必要があります。
ビンドリン

2

Python Oneライナー:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile

これにより、ファイル全体がメモリに丸lurみされ、OPの問題に適合しない可能性があります。順序を保持することも保証されない
-iruvar

提案のおかげで、私はちょうどPythonを学んでいます..学習目的でこれを試しました.. :)
Rahul Patil

ここだワンライナーではなく、(簡潔に)印刷に供給するためにファイル全体をメモリにロードしたり、単一の巨大な文字列を作成するのいずれかなしに順番を維持ユニークな行を返すPythonの2.7バージョン
iruvar

私は何かを持っている1_CR @おかげで:)今日学ぶOrderedDict
ラーフルパティル

0

ここでの答えはどれも私のMacではうまくいきませんでしたので、私に合ったシンプルなpythonスクリプトを書きました。先頭/末尾の空白を無視しており、メモリ消費も気にしません。

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

上記をunique.pyに保存し、次のように実行します。

python unique.py inputfile.txt outputfile.txt

-1

bash 4では、連想配列を利用する純粋なbashソリューションを使用できます。ここに例があります

unset llist; declare -A llist;
while read -r line; do
if [[ ${llist[$line]} ]]; then
  continue
else 
  printf '%s\n' "$line"
  llist[$line]="x"
fi
done < file.txt

2
read大きなテキストファイルを処理するためにループを使用しないでください。bashは、改行をオーバーシュートしないように、一度に1バイトずつ読み取る必要があります。また、bashは、一般にawkと比較してテキスト処理がそれほど速くありません。これを使用する場合read -ra、入力でバックスラッシュを食べないようにします。また、これをシェル関数に入れるか、対話的に使用する場合は、ループのunset llist 後を忘れないでください。
ピーターコーデス

2
@PeterCordes、またはあなただけ参照されている可能性があり、この :-)を
iruvar
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.