Remova linhas duplicadas, mantendo a ordem das linhas


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

O "" servidor "" possui: 8 GByte RAM + 16 GByte SWAP, x> 300 GByte de espaço livre, amd64, CPU de desktop. Scientific Linux 6.6. Nada mais funciona para fazer LOAD. O awk é interrompido após alguns segundos .. out.txt é ~ 1,6 GByte. GNU Awk 3.1.7.

Pergunta : Como posso remover as linhas duplicadas, mantendo a ordem das linhas? Caso é importante também, por exemplo: "A" e "a" são duas linhas diferentes, tem que mantê-lo. Mas "a" e "a" são duplicados, apenas o primeiro é necessário.

A resposta pode estar em qualquer coisa .. se o awk não for bom para isso .. então perl / sed .. qual poderia ser o 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]# 

Atualização: Eu tentei isso em uma máquina RHEL, ela não aborta, mas não tive tempo para esperar que ela terminasse. Por que o SL linux do SL difere do RHEL?

Atualização: Estou tentando um gues virtual do Ubuntu 14 .. até agora funciona! Não é um 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
Não há linhas duplicadas no seu exemplo ...?
mikeserv

1
O que são awkversões em duas máquinas?
precisa saber é

rhel atualizado e sl linux atualizado, não sei a versão rhel .. sl é: GNU Awk 3.1.7
somelooser28533

Quão grande é out.txt? O mesmo comando funciona se você tentar em um arquivo menor? Quantos usuários na máquina? Havia memória disponível suficiente para o processo? Existe algo de especial na linha 8547098 do arquivo de entrada?
terdon

Respostas:


22

Duvido que faça alguma diferença, mas, apenas no caso, veja como fazer a mesma coisa no Perl:

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

Se o problema for manter as linhas exclusivas na memória, isso terá o mesmo problema que awkvocê tentou. Portanto, outra abordagem poderia ser:

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

Como funciona:

  1. Em um sistema GNU, cat -no número da linha será anexado a cada linha, seguindo uma quantidade de espaços e seguido por um caractere <tab> . catcanaliza essa representação de entrada para sort.

  2. sortA -k2opção de instrui apenas a considerar os caracteres do segundo campo até o final da linha ao classificar e sortdivide os campos por padrão no espaço em branco (ou catnos espaços inseridos e <tab> ) .
    Quando seguido -k1n, sortconsidera o segundo campo primeiro e, em seguida, em segundo lugar - no caso de -k2campos idênticos - considera o 1º campo, mas classificado numericamente. Assim, as linhas repetidas serão classificadas juntas, mas na ordem em que apareceram.

  3. Os resultados são direcionados para uniq- que são instruídos a ignorar o primeiro campo ( -f1- e também separados por espaços em branco) - e resultam em uma lista de linhas exclusivas no arquivo original e são direcionados novamente para sort.
  4. Esse tempo é sortclassificado numericamente no primeiro campo ( catnúmero da linha inserido) , retornando a ordem de classificação ao que estava no arquivo original e canalizando esses resultados cut.
  5. Por fim, cutremove os números de linha que foram inseridos por cat. Isso é feito cutimprimindo apenas do 2º campo até o final da linha (e cuto delimitador padrão é um caractere <tab> ) .

Ilustrar:

$ 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

Oi Terdon, o OP precisa manter a ordem das linhas, para que o método cat | sort | uniq não funcione ... Como a sua versão perl ...
Lambert

1
Ótima solução com sort! Mas a maioria sortpode fazer uniqpor si mesmo para que você possa curta você script sort -uk2 | sort -bk1,1n
Costas

@Costas é mais sort? Eu pensei que -uera um recurso GNU.
terdon

@don_crissti ah, assim é, obrigado. Como eu poderia usá-lo aqui? Como acabei de notar (e editado para corrigir), preciso classificar primeiro no segundo campo e depois no primeiro numericamente para manter a ordem das linhas. Como posso usar -ue especificar que ele deve ignorar o 1º campo? De acordo com man sort, essa -unão é uma das opções possíveis -f, então não acho que possa ser usada aqui.
terdon

1
essa é a transformação schwartziana ! (+1)
JJoao

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

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

EDIT 1: Isso realmente funciona? (comparando)

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

Caso1 : 100_000_000 números aleatórios (5 dígitos cada), 566Mbytes, 31_212 valores diferentes:

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

Caso 2 : 50_000_000 números de rand (10 dígitos cada), 516Mbytes, 48_351_464 valores diferentes:

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

(os seguintes números não são muito precisos):

┌────────┬────────┬────────────────┬────────┬──────┐
         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 com 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 }

A classificação também pode ser otimizada, adicionando uma opção de tamanho do cache (não concluída).

Uma rápida conclusão:

  • sort é um comando fantástico!

1
sort -uk2e sort -nk1,1são diferentes. O primeiro considera da tecla 2cd até o final da linha, o segundo considera apenas a primeira tecla. Você deve mudar o seu sort -nk1lá - pode até ser mais rápido assim, mas com certeza será mais confiável. A propósito - essas são algumas caixas bonitas.
mikeserv

@ MikeServ, obrigado pelo comentário. Como K1,1 é único, os tipos -nk1 e -nk1,1 retornam alguns resultados. Tentei os dois, o resultado foi o mesmo e o tempo não foi diferente.
JJoao

Isso faz sentido - obrigado por tentar, no entanto. O mesmo cat -nacontece com uma guia ? Não sei como esse comando funciona.
mikeserv

1
@mikeserv, felizmente cat -ntransfrom cada lineem spaces + the number + \t + line- o formato ideal para classificar e corte
JJoao

1

Eu usei

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

BINMODE = rw: para manter felizes os terminadores de fim de linha. (Eu moro em um ambiente misto)

A lógica é simples.

Se a linha atual não estiver na matriz associativa, adicione-a à matriz associativa e imprima na saída.

Pode haver limitações de memória com essa abordagem. Para arquivos e conjuntos de arquivos muito grandes, usei variações sobre isso, usando o armazenamento de arquivos para superar as limitações.


0

A semântica de preservação de ordem do seu problema tem uma propriedade maravilhosa: você pode subdividir o problema. Você pode fazer split -l 1000000no arquivo de entrada; as peças de 1000000 linhas produzidas têm nomes lexicamente ordenados, o que é bom; uniqify as peças; e então (como uma segunda passagem) uniqifique as saídas dessas.

Isso resolve o problema de falta de memória (limitando o requisito de memória) às custas de transformá-lo em uma solução multipass.

Especificamente:

Gere dados de entrada:

$ 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

Divida os dados de entrada:

$ 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

Execute o uniqificador de uma só vez (mantém todas as linhas de entrada exclusivas na memória):

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

Execute o uniqificador em partes divididas (retém apenas linhas de entrada exclusivas de cada parte na memória) e reduza como uma segunda passagem:

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

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

Comparar:

$ 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

Não conheço a proporção de linhas únicas para não exclusivas em sua entrada, nem quão bem misturadas são as linhas de entrada - portanto, há algumas opções a serem feitas em termos do número de arquivos divididos que você precisa.


0

Outra abordagem (que vale a pena postar como resposta separada) é: em vez da abordagem de arquivos divididos que cria arquivos temporários, faça os lotes no próprio software uniqifier. Por exemplo, usando uma implementação de uniqifier Ruby para fins explicativos:

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

A idéia é limpar o conjunto de hash de vez em quando. Então isso se torna 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

Portanto, você pode executar esta versão limitada repetidamente, até que a contagem de linhas não mude de uma iteração para a seguinte.

Observe que essa técnica capped-uniqm é independente da linguagem: você pode limpar a lines_seenmatriz a cada N linhas, esteja usando awk, python, perl, C ++, etc. Existem métodos de limpeza de conjunto para todas essas linguagens; Eu acredito awk's deleteé não-padrão, mas comum.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.