Hapus garis duplikat sambil menjaga urutan garis

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

"" Server "" memiliki: 8 GByte RAM + 16 GByte SWAP, x> 300 GByte ruang kosong, amd64, desktop CPU. Linux Ilmiah 6.6. Tidak ada lagi yang dijalankan untuk membuat LOAD. Awk batal setelah beberapa detik .. out.txt adalah ~ 1,6 GByte. GNU Awk 3.1.7.

Pertanyaan : Bagaimana saya bisa menghapus garis duplikat sambil menjaga urutan garis? Kasus juga penting, misalnya: "A" dan "a" adalah dua baris yang berbeda, harus tetap dipertahankan. Tapi "a" dan "a" adalah duplikat, hanya yang pertama diperlukan.

Jawaban bisa dalam apa saja .. jika awk tidak baik untuk ini .. maka perl / sed .. apa masalahnya?

[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]# 

Pembaruan: Saya mencoba ini pada mesin RHEL, tidak dibatalkan, tetapi saya tidak punya waktu untuk menunggu sampai selesai .. mengapa SL linux berbeda dari RHEL?

Pembaruan: Saya mencoba pada Ubuntu 14 virtual gues .. sejauh ini berhasil! Ini bukan masalah 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:~# 
somelooser28533
sumber
2
Tidak ada garis duplikat dalam contoh Anda ...?
mikeserv
1
Apa awkversi dalam dua mesin?
cuonglm
rhel terbaru dan mutakhir sl linux, tidak tahu versi rhel .. sl adalah: GNU Awk 3.1.7
somelooser28533
Seberapa besar out.txt? Apakah perintah yang sama berfungsi jika Anda mencobanya pada file yang lebih kecil? Berapa banyak pengguna di mesin? Apakah ada cukup memori yang tersedia untuk proses ini? Apakah ada yang spesial tentang baris 8547098 dari file input?
terdon

Jawaban:

22

Saya ragu itu akan membuat perbedaan tetapi, untuk berjaga-jaga, inilah cara melakukan hal yang sama di Perl:

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

Jika masalahnya adalah menjaga garis-garis unik dalam memori, itu akan memiliki masalah yang sama dengan yang awkAnda coba. Jadi, pendekatan lain dapat:

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

Bagaimana itu bekerja:

  1. Pada sistem GNU, cat -nakan menambahkan nomor baris ke setiap baris mengikuti sejumlah spasi dan diikuti oleh karakter <tab> . catmem-pipe representasi input ini ke sort.

  2. sort's -k2pilihan menginstruksikan hanya mempertimbangkan karakter dari kolom kedua sampai akhir baris ketika menyortir, dan sortperpecahan bidang secara default pada white-space (atau cat' s dimasukkan ruang dan <tab> ) .
    Ketika diikuti oleh -k1n, sortpertimbangkan bidang ke-2 terlebih dahulu, dan kemudian yang kedua — dalam kasus -k2bidang yang identik — bidang itu menganggap bidang ke-1 tetapi sebagai diurutkan secara numerik. Jadi garis yang diulang akan diurutkan bersama tetapi dalam urutan yang muncul.

  3. Hasilnya disalurkan ke uniq— yang diperintahkan untuk mengabaikan bidang pertama ( -f1- dan juga dipisahkan oleh spasi) —dan yang menghasilkan daftar baris unik di file asli dan disalurkan kembali ke sort.
  4. Kali ini sortmengurutkan pada bidang pertama ( catnomor baris yang disisipkan) secara numerik, mendapatkan urutan kembali ke apa yang ada di file asli dan pipa hasil ini ke cut.
  5. Terakhir, cutmenghapus nomor baris yang dimasukkan oleh cat. Ini dipengaruhi oleh cutpencetakan hanya dari bidang ke-2 hingga akhir baris (dan cutpembatas default adalah karakter <tab> ) .

Menggambarkan:

$ 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
sumber
Hai Terdon, OP perlu menjaga urutan garis, sehingga metode cat | sort | uniq tidak akan berfungsi ... Seperti versi perl Anda ...
Lambert
1
Solusi yang bagus dengan sort! Tetapi sebagian besar sortdapat melakukannya uniqsendiri sehingga Anda dapat mempersingkat skrip Anda dengan sort -uk2 | sort -bk1,1n
Costas
@Costas apakah itu yang paling sort? Saya pikir -uitu fitur GNU.
terdon
@don_crissti ah, begitulah, terima kasih. Bagaimana saya bisa menggunakannya di sini? Seperti yang baru saja saya perhatikan (dan diedit untuk memperbaiki), saya perlu mengurutkan pada bidang ke-2 terlebih dahulu dan kemudian pada tanggal 1 secara numerik untuk menjaga urutan garis. Bagaimana saya bisa menggunakan -udan menentukan bahwa itu harus mengabaikan bidang 1? Menurut man sort, -uini bukan salah satu opsi yang mungkin untuk -f, jadi saya tidak berpikir itu bisa digunakan di sini.
terdon
1
ini adalah transformasi Schwartzian ! (+1)
JJoao
7
#!/usr/bin/perl 
use DB_File;
tie %h, 'DB_File';

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

EDIT 1: Apakah ini benar-benar berfungsi? (perbandingan)

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

Case1 : 100_000_000 angka acak (5 digit masing-masing), 566Mbytes, 31_212 nilai yang berbeda:

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

Kasus 2 : 50_000_000 angka rand (masing-masing 10 digit), 516Mbytes, 48_351_464 nilai yang berbeda:

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

(angka-angka berikut ini tidak terlalu tepat):

┌────────┬────────┬────────────────┬────────┬──────┐
         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 dengan cache adalah:

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 }

Sortir juga dapat dioptimalkan dengan menambahkan opsi cache (tidak dilakukan).

Satu kesimpulan cepat:

  • sort adalah perintah yang fantastis!
Joao
sumber
1
sort -uk2dan sort -nk1,1berbeda. Yang pertama mempertimbangkan dari kunci 2cd ke akhir baris, yang kedua hanya mempertimbangkan kunci pertama. Anda harus mengubahnya di sort -nk1sana - bahkan mungkin lebih cepat, tetapi pasti akan lebih andal. By the way - itu adalah beberapa kotak cantik.
mikeserv
@ mikeserv, terima kasih atas komentarnya. Karena K1, 1 adalah unik, urutkan -nk1 dan urutkan -nk1, 1 mengembalikan hasil. Saya mencoba keduanya, hasilnya sama dan waktunya tidak berbeda.
JJoao
Itu masuk akal - terima kasih sudah mencobanya. Begitu cat -njuga tab ? Saya tidak tahu bagaimana perintah itu bekerja.
mikeserv
1
@ mikeserv, dengan senang hati mentransfromikan cat -nmasing-masing linedalam spaces + the number + \t + line- format ideal untuk sortir dan memotong
JJoao
1

Saya sudah menggunakan

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

BINMODE = rw: untuk menjaga terminator garis akhir tetap bahagia. (Saya tinggal di lingkungan os campuran)

Logikanya sederhana.

Jika baris saat ini tidak dalam array asosiatif kemudian tambahkan ke array asosiatif dan cetak ke output.

Mungkin ada keterbatasan memori dengan pendekatan ini. Untuk file yang sangat besar dan kumpulan file, saya telah menggunakan variasi ini, menggunakan penyimpanan file untuk melewati batasan.

John
sumber
0

Semantik yang mempertahankan pesanan dari masalah Anda memiliki properti yang luar biasa: Anda dapat membagi masalah tersebut. Anda dapat melakukannya split -l 1000000pada file input; potongan-potongan 1000000-line yang dihasilkannya memiliki nama yang dipesan secara leksikal yang bagus; kemudian uniqify bagian; dan kemudian (sebagai pass kedua) menyatukan output dari mereka.

Ini menyelesaikan masalah kehabisan memori (dengan membatasi kebutuhan memori) dengan mengorbankan mengubahnya menjadi solusi multipas.

Secara khusus:

Hasilkan data input:

$ 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

Pisahkan data input:

$ 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

Jalankan uniqifier sekaligus (mempertahankan semua jalur input unik dalam memori):

# '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

Jalankan uniqifier pada bagian-bagian yang terpisah (hanya mempertahankan jalur input unik dari masing-masing bagian dalam memori), lalu kurangi sebagai lintasan kedua:

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

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

Membandingkan:

$ 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

Saya tidak tahu rasio garis unik dan non-unik dalam input Anda, atau seberapa baik garis input tercampur - jadi ada beberapa penyesuaian yang harus dilakukan dalam hal jumlah file split yang Anda butuhkan.

John Kerl
sumber
0

Pendekatan lain (layak posting sebagai jawaban terpisah) adalah: alih-alih pendekatan split-file yang membuat file temp, lakukan batching dalam perangkat lunak uniqifier itu sendiri. Misalnya, menggunakan implementasi uniqifier Ruby untuk tujuan penjelasan:

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

Idenya adalah untuk menghapus hash-set sesekali. Maka ini menjadi berulang:

$ 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

Jadi, Anda dapat menjalankan versi yang dibatasi ini berulang kali, hingga jumlah baris tidak berubah dari satu iterasi ke yang berikutnya.

Perhatikan bahwa teknik capped-uniqm ini tidak tergantung pada bahasa: Anda dapat menghapus lines_seenlarik setiap baris N apakah Anda menggunakan awk, python, perl, C ++, dll. Ada metode set-clear untuk semua bahasa ini; Saya percaya awk's deleteadalah non-standar tapi umum.

John Kerl
sumber