行の順序を維持しながら重複行を削除する


14
[root@server]# awk '!seen[$0]++' out.txt > cleaned
awk: (FILENAME=out.txt FNR=8547098) fatal error: internal error
Aborted
[root@server]#

「サーバー」には、8ギガバイトのRAM + 16ギガバイトのスワップ、x> 300ギガバイトの空き領域、amd64、デスクトップCPUがあります。Scientific Linux 6.6。LOADを作成するために他に何も実行されません。数秒後にawkは異常終了します。out.txtは約1.6ギガバイトです。GNU Awk 3.1.7。

質問:行の順序を維持しながら重複行を削除するにはどうすればよいですか?大文字と小文字の区別も重要です。例:「A」と「a」は2つの異なる行で、保持する必要があります。ただし、「a」と「a」は重複しています。最初の1つだけが必要です。

答えは何でも構いません.. awkがこれに向いていない場合.. perl / sed ..問題は何でしょうか

[root@server]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61945
max locked memory       (kbytes, -l) 99999999
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 99999999
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61945
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
[root@server]# 

更新:RHELマシンでこれを試しましたが、中断しませんが、終了するのを待つ時間がありませんでした。SLlinuxがRHELと異なるのはなぜですか?

更新:Ubuntu 14の仮想guesを試しています。ulimitの問題ではありません:mawk 1.3.3

root@asdf-VirtualBox:~# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 51331
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 51331
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
root@asdf-VirtualBox:~# 

2
あなたの例には重複した行はありません...?
mikeserv

1
awk2台のマシンのバージョンとは何ですか?
cuonglm

最新のrhelおよび最新のsl linux、rhelのバージョンがわからない
.sl

大きさはout.txt?小さいファイルで試しても同じコマンドは機能しますか?マシン上のユーザーは何人ですか?プロセスに十分なメモリがありましたか?入力ファイルの8547098行について特別なことはありますか?
テルドン

回答:


22

私はそれが違いを生むとは思いませんが、念のため、Perlで同じことを行う方法を以下に示します。

perl -ne 'print if ++$k{$_}==1' out.txt

問題がメモリ内の一意の行を保持している場合、それはawkあなたが試みたのと同じ問題を抱えています。したがって、別のアプローチは次のとおりです。

cat -n out.txt | sort -k2 -k1n  | uniq -f1 | sort -nk1,1 | cut -f2-

使い方:

  1. GNUシステムでcat -nは、いくつかのスペースに続く各行に行番号を付加し、その後に<tab>文字が続きますcatこの入力表現をにパイプしますsort

  2. sort-k2オプションは、ソート時に2番目のフィールドから行末までの文字のみを考慮するように指示しsort、デフォルトで空白(またはcat挿入されたスペースと<tab>)でフィールドを分割します。
    後に続く場合-k1nsort最初に2番目のフィールドを考慮し、次に2番目に(同一-k2フィールドの場合)、最初のフィールドを考慮しますが、数値的にソートされます。そのため、繰り返される行は一緒に並べ替えられますが、出現順に並べられます。

  3. 結果はパイプされますuniq—最初のフィールド-f1 -またとして空白で区切られた) -および元のファイル内の一意の行のリストのどの結果とにパイプバックありますsort
  4. 今回sortは、最初のフィールドcatの挿入された行番号)を数値でソートし、ソート順を元のファイルにあったものに戻し、これらの結果をにパイプしcutます。
  5. 最後に、cutによって挿入された行番号を削除しcatます。これはcut、2番目のフィールドから行の終わりまでのみを印刷することによって行われます(およびcutデフォルトの区切り文字は<tab>文字です)

説明する:

$ cat file
bb
aa
bb
dd
cc
dd
aa
bb
cc
$ cat -n file | sort -k2 | uniq -f1 | sort -k1 | cut -f2-
bb
aa    
dd
cc

こんにちはTerdon、OPのニーズ猫ので、行の順序を維持する|並べ替え| uniqのメソッドは動作しません... ...しかし、あなたのperlのバージョンと同じように
ランバート

1
と素敵なソリューションsort!しかし、ほとんどはsort行うことができuniq、それ自体であなたは短いことができますスクリプトによって sort -uk2 | sort -bk1,1n
コスタス

@Costasが一番sortですか?-uGNU機能だと思った。
テルドン

@don_crisstiああ、そうです、ありがとう。どうすればここで使用できますか?気付いた(修正するために編集した)ので、最初に2番目のフィールドでソートし、次に1番目のフィールドでソートして、行の順序を維持する必要があります。次に-u、最初のフィールドを無視するように指定するにはどうすればよいですか?によるとman sort-uはの可能なオプションの1つではないため-f、ここで使用できるとは思わない。
テルドン

1
これはシュワルツ変換です!(+1)
JJoao

7
#!/usr/bin/perl 
use DB_File;
tie %h, 'DB_File';

while(<>){ not $h{$_} and print and $h{$_}=1 }

編集1:それは本当に機能しますか?(比較)

Sol1 : Terdon et all Schwartzian-transform-like one-liner
    cat -n _1 | sort -uk2 | sort -nk1 | cut -f2-

Sol2 : perl  + DB_File (this answer)
    perl dbfile-uniq _1

Sol3 : PO (John W. Gill solution has a similar behavior)
    awk '!seen[$0]++' _1

Sol4: Terdon perl
    perl -ne 'print if ++$k{$_}==1' _1

ケース1:100_000_000の乱数(各5桁)、566Mバイト、31_212の異なる値:

$ while true ; do echo $RANDOM; done | head -100000000 > _1

事例2:50_000_000ランド番号(各10桁)、516Mバイト、48_351_464の異なる値:

$ shuf _1 |  sed 'N;s/\n/ /' > _11

(次の数値はあまり正確ではありません):

┌────────┬────────┬────────────────┬────────┬──────┐
         Sol1    Sol2            Sol3    Sol4 
         sort...│ perl DB         awk     perl 
├────────┼────────┼────────────────┼────────┼──────┤
 case 1  6m15    6m17            0m28    0m28 
├────────┼────────┼────────────────┼────────┴──────┤
 case 2  11m15   81m44           out of memory 
├────────┼────────┼────────────────┼────────┬──────┤
 case 2          5m54 /cache=2G               
└────────┴────────┴────────────────┴────────┴──────┘

キャッシュ付きのsol2は次のとおりです。

use DB_File;
use Fcntl ;

$DB_HASH->{'cachesize'} = 2000_000_000;
tie %h, 'DB_File', "_my.db", O_RDWR|O_CREAT|O_TRUNC, 0640, $DB_HASH;

while(<>){ not $h{$_} and print and $h{$_}=1 }

並べ替えは、cachesizeオプションを追加して最適化することもできます(未完了)。

簡単な結論:

  • sort 素晴らしいコマンドです!

1
sort -uk2sort -nk1,1は異なります。最初は2cdキーから行末までを考慮し、2番目は最初のキーのみを考慮します。そこを変更する必要がありますsort -nk1-それはそのように高速かもしれませんが、それは間違いなくより信頼できるでしょう。ところで-これらはいくつかのきれいな箱です。
mikeserv

@mikeserv、コメントありがとうございます。K1,1は一意であるため、sort -nk1およびsort -nk1,1は結果を返します。両方試してみましたが、結果は同じであり、時間は明確ではありませんでした。
JJoao

それは理にかなっています-それを試してくれてありがとう。だから、cat -nないタブが?そのコマンドがどのように機能するかわかりません。
mikeserv

1
@mikeserv、喜んcat -nそれぞれtransfrom linespaces + the number + \t + lineソートし、カットのための理想的な形式-
JJoao

1

私は使った

awk -v BINMODE=rw '!($0 in a){a[$0];print}' infile >> outfile

BINMODE = rw:行末ターミネーターを満足に保つため。(私は混合OS環境に住んでいます)

ロジックは簡単です。

現在の行が連想配列にない場合は、連想配列に追加して出力に出力します。

このアプローチにはメモリ制限がある場合があります。非常に大きなファイルとファイルのセットについては、ファイルストレージを使用して制限を超えた、これに関するバリエーションを使用しました。


0

問題の順序を維持するセマンティクスにはすばらしい特性があります。問題を細分化できます。できるよsplit -l 1000000、入力ファイルに。それが生成する1000000行の断片には、字句順に並べられた名前があります。次に、断片を一意化します。そして(2番目のパスとして)それらの出力を一意にします。

これにより、メモリ不足の問題は(メモリ要件を制限することにより)解決されますが、それを犠牲にしてマルチパスソリューションになります。

具体的には:

入力データを生成します。

$ cat make-uniqm-input.py
#!/usr/bin/env python
import random
n = 1000000
for i in xrange(0, n):
    print random.randint(1000, 2000)

$ python make-uniqm-input.py  > uniqm-input.txt

$ wc -l uniqm-input.txt
 1000000 uniqm-input.txt

入力データを分割します。

$ split -l 10000 uniqm-input.txt

$ ls x?? | head
xaa
xab
xac
xad
xae
xaf
xag
xah
xai
xaj

$ ls x?? | wc -l
     100

$ cat x?? | wc -l
 1000000

uniqifierを一度にすべて実行します(メモリ内のすべての一意の入力行を保持します)。

# 'uniqm' is any order-preserving uniq implementation, such as
# gawk '!counts[$0]++'.
$ uniqm < uniqm-input.txt > output-no-splitting.txt

$ wc -l output-no-splitting.txt
    1001 output-no-splitting.txt

分割されたピースに対してuniqifierを実行し(メモリ内の各ピースからの一意の入力行のみを保持します)、2回目のパスとして縮小します。

$ for x in x??; do uniqm < $x; done | uniqm > output-with-splitting.txt

$ wc -l output-with-splitting.txt
    1001 output-with-splitting.txt

比較する:

$ diff output-no-splitting.txt output-with-splitting.txt

$ head uniqm-input.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

$ head output-with-splitting.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

入力の一意の行と一意でない行の比率も、入力行がどれだけうまく混合されているかもわかりません。したがって、必要な分割ファイルの数に関して調整する必要があります。


0

別のアプローチ(別の回答として投稿する価値があります)は、一時ファイルを作成する分割ファイルアプローチの代わりに、uniqifierソフトウェア自体でバッチ処理を行います。たとえば、説明のためにRuby uniqifier実装を使用する:

require 'set'
line_batch_count = 50000 # tunable parameter
lines_seen = Set.new
line_number = 0
ARGF.each do |line|
   line_number += 1
   if (line_number % line_batch_count) == 0
     lines_seen.clear
   end
   unless lines_seen.include? line
      puts line
      lines_seen << line
   end
end

アイデアは、ハッシュセットを時々クリアすることです。その後、これは反復的になります:

$ cat uniqm-input.txt | ruby uniqm-capped.rb | wc -l
   20021

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | wc -l
    1001

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | head
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

したがって、行数が1つの反復から次の反復に変更されないまで、この上限付きバージョンを繰り返し実行できます。

このcapd-uniqmの手法は言語に依存しないことに注意してください。awk lines_seen、python、perl、C ++などを使用しているかどうかにかかわらず、N行ごとに配列をクリアできます。これらすべての言語にはset-clearメソッドがあります。私は信じているawks「はdelete非標準ますが一般的です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.