Rimuovere le linee duplicate mantenendo l'ordine delle linee


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

Il "" server "" ha: 8 GByte RAM + 16 GByte SWAP, x> 300 GByte spazio libero, amd64, CPU desktop. Scientific Linux 6.6. Nient'altro ci scorre sopra per fare LOAD. Awk si interrompe dopo alcuni secondi .. out.txt è ~ 1,6 GByte. GNU Awk 3.1.7.

Domanda : come posso rimuovere le linee duplicate mantenendo l'ordine delle linee? Anche il caso è importante, ad esempio: "A" e "a" sono due linee diverse, devono essere mantenute. Ma "a" e "a" sono duplicati, è necessario solo il primo.

La risposta potrebbe essere in qualsiasi cosa .. se awk non è buono per questo .. allora perl / sed .. quale potrebbe essere il problema?

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

Aggiornamento: l'ho provato su una macchina RHEL, non si interrompe, ma non ho avuto il tempo di aspettare che finisse .. perché SL Linux non differisce da RHEL?

Aggiornamento: sto provando su un Ubuntu 14 guide virtuali .. finora funziona! Non è un problema 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
Non ci sono righe duplicate nel tuo esempio ...?
Mikeserv,

1
Quali sono le awkversioni in due macchine?
cuonglm,

rhel aggiornato e sl linux aggiornato, non conosco la versione rhel .. sl è: GNU Awk 3.1.7
somelooser28533

Quanto è grande out.txt? Lo stesso comando funziona se lo provi su un file più piccolo? Quanti utenti sulla macchina? C'era abbastanza memoria disponibile per il processo? C'è qualcosa di speciale nella riga 8547098 del file di input?
terdon

Risposte:


22

Dubito che farà la differenza ma, nel caso, ecco come fare la stessa cosa in Perl:

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

Se il problema è mantenere in memoria le righe univoche, ciò avrà lo stesso problema che awkhai provato. Quindi, un altro approccio potrebbe essere:

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

Come funziona:

  1. Su un sistema GNU, cat -nanteporrà il numero di riga a ciascuna riga seguendo un certo numero di spazi e seguito da un carattere <tab> . catreindirizza questa rappresentazione di input a sort.

  2. sortL' -k2opzione indica di considerare solo i caratteri dal secondo campo fino alla fine della riga durante l'ordinamento e sortdivide i campi per impostazione predefinita nello spazio bianco (o negli catspazi inseriti e <tab> ) .
    Quando seguito da -k1n, sortconsidera prima il 2 ° campo e poi - nel caso di -k2campi identici - considera il 1 ° campo ma ordinato numericamente. Quindi le linee ripetute verranno ordinate insieme ma nell'ordine in cui sono apparse.

  3. I risultati vengono reindirizzati a uniq- a cui viene detto di ignorare il primo campo ( -f1- e anche come separati da spazi bianchi) - e che risulta in un elenco di righe univoche nel file originale e viene reindirizzato a sort.
  4. Questa volta sortordina il primo campo ( catil numero di riga inserito) numericamente, riportando l'ordinamento a quello che era nel file originale e reindirizzando questi risultati cut.
  5. Infine, cutrimuove i numeri di riga che sono stati inseriti da cat. Ciò viene effettuato cutstampando solo dal 2 ° campo fino alla fine della riga (e cutil delimitatore predefinito è un carattere <tab> ) .

Illustrare:

$ 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

Ciao Terdon, l'OP deve mantenere l'ordine delle linee, quindi il metodo cat | sort | uniq non funzionerà ... Come la tua versione perl ...
Lambert

1
Bella soluzione con sort! Ma la maggior parte sortpuò fare uniqda sola in modo da poter abbreviare la sceneggiatura di sort -uk2 | sort -bk1,1n
Costas

@Costas è di più sort? Pensavo -ufosse una funzionalità GNU.
terdon

@don_crissti ah, così è, grazie. Come potrei usarlo qui? Come ho appena notato (e modificato per risolvere), ho bisogno di ordinare prima sul 2 ° campo e poi sul 1 ° numericamente per mantenere l'ordine delle righe. Come posso quindi utilizzare -ue specificare che dovrebbe ignorare il 1 ° campo? Secondo man sort, -unon è una delle possibili opzioni per -f, quindi non credo che possa essere usato qui.
terdon

1
questa è la trasformazione di Schwartz ! (+1)
JJoao

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

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

EDIT 1: funziona davvero? (a confronto)

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

Caso 1 : 100_000_000 numeri casuali (5 cifre ciascuno), 566 MB, 31_212 valori diversi:

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

Caso 2 : 50_000_000 numeri di rand (10 cifre ciascuno), 516 MB, 48_351_464 valori diversi:

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

(i seguenti numeri non sono molto precisi):

┌────────┬────────┬────────────────┬────────┬──────┐
         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 con cache è:

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 }

L'ordinamento può anche essere ottimizzato aggiungendo un'opzione di memorizzazione nella cache (non eseguita).

Una breve conclusione:

  • sort è un comando fantastico!

1
sort -uk2e sort -nk1,1sono diversi. Il primo considera dalla chiave 2cd alla fine della riga, il secondo considera solo la prima chiave. Dovresti cambiare il tuo sort -nk1lì - potrebbe anche essere più veloce in quel modo, ma sarà sicuramente più affidabile. A proposito: quelle sono delle belle scatole.
Mikeserv,

@mikeserv, grazie per il commento. Poiché K1,1 è univoco, sort -nk1 e sort -nk1,1 restituiscono il risultato. Ho provato entrambi, il risultato è stato lo stesso e il tempo non è stato distintivo.
JJoao

Questo ha senso - grazie per averlo provato, però. Quindi cat -nfa una scheda ? Non so come funzioni quel comando.
Mikeserv,

1
@mikeserv, trascriviamo felicemente cat -nciascuno linein spaces + the number + \t + line- il formato ideale per ordinare e tagliare
JJoao

1

Ho usato

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

BINMODE = rw: per rendere felici i terminatori di fine linea. (Vivo in un ambiente os misto)

La logica è semplice.

Se la riga corrente non è nella matrice associativa, aggiungerla alla matrice associativa e stampare sull'output.

Ci possono essere limiti di memoria con questo approccio. Per file e insiemi di file molto grandi, ho usato variazioni su questo, usando l'archiviazione dei file per superare le limitazioni.


0

La semantica che preserva l'ordine del tuo problema ha una proprietà meravigliosa: puoi suddividere il problema. Puoi fare split -l 1000000sul file di input; i pezzi di 1000000 linee che produce hanno nomi ordinati per lessico che è buono; quindi unificare i pezzi; e quindi (come secondo passaggio) unificare gli output di quelli.

Ciò risolve il problema di memoria insufficiente (limitando il requisito di memoria) a scapito di trasformarlo in una soluzione multipass.

In particolare:

Genera dati di 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

Suddividere i dati di 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

Esegui l'UnicoQifier in una sola volta (conserva tutte le righe di input univoche in memoria):

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

Esegui l'unificatore su pezzi divisi (mantiene solo le righe di input univoche da ciascun pezzo in memoria), quindi riduci come secondo passaggio:

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

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

Confrontare:

$ 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

Non conosco il rapporto tra linee uniche e non uniche nel tuo input, né quanto siano ben miste le linee di input - quindi c'è qualche ottimizzazione da fare in termini di numero di file divisi che ti servono.


0

Un altro approccio (che vale la pena pubblicare come risposta separata) è: invece dell'approccio a file diviso che crea file temporanei, eseguire il batch all'interno del software uniqifier stesso. Ad esempio, utilizzando un'implementazione di un uniqifier di Ruby a scopo esplicativo:

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

L'idea è di cancellare l'hash-set ogni tanto. Quindi questo diventa iterativo:

$ 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

Quindi è possibile eseguire ripetutamente questa versione ridotta, fino a quando il conteggio delle righe non passa da una iterazione alla successiva.

Si noti che questa tecnica capped-uniqm è indipendente dalla lingua: è possibile cancellare l' lines_seenarray ogni N righe sia che si utilizzi awk, python, perl, C ++, ecc. Esistono metodi set-clear per tutte queste lingue; Credo che awk's deleteè non-standard, ma comune.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.