uniq um arquivo csv ignorando uma coluna, talvez awk?

7

Dado este arquivo (as anotações não fazem parte do arquivo, mas fazem parte da explicação) ...

x,a,001,b,c,d,y
x,a,002,b,c,e,yy
x,bb,003,b,d,e,y
x,c,004,b,d,e,y
x,c,005,b,d,e,y   # nb - dupe of row 4
x,dd,006,b,d,e,y
x,c,007,b,d,e,y   # nb - dupe of row 4 and 5
x,dd,008,b,d,f,y
x,dd,009,b,d,e,y   # nb - dupe of row 6
x,e,010,b,d,f,y

... Gostaria de obter a seguinte saída:

x,a,001,b,c,d,y
x,a,002,b,c,e,yy
x,bb,003,b,d,e,y
x,c,004,b,d,e,y
x,dd,006,b,d,e,y
x,dd,008,b,d,f,y
x,e,010,b,d,f,y

Se a coluna 3 fosse cortada do arquivo e, em seguida, o uniq fosse executado sobre o arquivo, se as linhas restantes tivessem seu valor de coluna três adicionado novamente no lugar certo, eu obteria o resultado acima.

Mas eu estou realmente lutando, para chegar a algo que faria isso. Eu gostaria de receber uma oportunidade para aprender sobre os utilitários de processamento de texto do Linux.

Desempenho: os arquivos provavelmente não crescem para mais de 1 MB e há apenas um arquivo por dia.

Destino: Debian GNU / Linux 7 amd64, 256MB / Xeon.

Editar: exemplo modificado, pois os campos não são de largura fixa, e uma solução envolvendo uniq --skip-chars=n não funcionará até onde eu saiba.

    
por jon 17.09.2013 / 12:11

4 respostas

18

Com awk , você poderia fazer:

awk -F, -vOFS=, '{l=$0; $3=""}; ! ($0 in seen) {print l; seen[$0]}'
    
por 17.09.2013 / 13:02
7

A maneira mais simples :

sort -u -t, -k1,2 -k4
  • -u : gera apenas a primeira linha de iguais
  • -t, : use vírgula como separador de campos
  • -k1,2 -k4 : classifique apenas nos campos 1,2 e 4 e o restante

Outra opção é reorganizar os dados com sed (observe a opção GNU -r ) em ambos os lados - isso requer que os registros sejam na maior parte de comprimento fixo, caso contrário, falhará (e apenas dificilmente notavelmente): / p>

sed -r       's/^([^,]+,[^,]+)(,[^,]+)(.*)$//' \
    | sort \
    | uniq -w 12 \
    | sed -r 's/^([^,]+,[^,]+)(.*)(,[^,]+)$//'

Você pode querer adicionar outro sort no final para encomendá-lo pelos números, se desejar (use a opção -k para selecionar de acordo com o que a classificação deve ser executada - ou seja, algo como sed -k3 -t, )

Em Perl, você poderia, por exemplo, usar as partes nas quais deseja decidir a exclusividade como chaves em um hash (os valores as linhas inteiras) e inserir no hash somente se a chave ainda não estiver definida. Claro que isso será muito mais flexível do que usar sed (ou awk ), mas também mais escrita (estou longe de ser um Perl Guru, então é muito provável que possa ser feito de uma maneira muito mais elegante - veja outras respostas para soluções Perl-like Perl):

#!/usr/bin/perl
use strict;

my %lines;
while (<>) {
    (my $k1, my $v, my $k2) = /^([^,]+,[^,]+,)([^,]+)(,.*)$/;
    my $k = $k1 . $k2;
    if (!exists($lines{$k})) {
        $lines{$k} = $_;
    }
}

for my $k (sort(keys(%lines))) {
    print $lines{$k};
}
    
por 17.09.2013 / 12:37
3

Uma maneira de fazer isso com awk | sort | uniq | awk :

awk -F, '{a=$1;$1=$3;$3=a;print}' file | sort -k 2 | uniq -f 1 | awk -v OFS=',' '{a=$1;$1=$3;$3=a;print}'
    
por 17.09.2013 / 13:24
2

Um caminho Perl mais simples seria:

perl -F"," -ane '$a=join(",",@F[0,1,3 .. $#F]); print unless $k{$a}; $k{$a}++' file

O -a divide os campos na matriz @F e -F"," define o delimitador de campo como , . -n significa executar o script fornecido por -e em cada linha do arquivo de entrada.

A idéia é pegar um array array (elementos 0,1 e 3 até o final do array), juntá-los em uma string ( $a ) e usar essa string como uma referência hash (array associativo). Você então imprime cada linha apenas se a chave hash não foi vista antes.

    
por 17.09.2013 / 17:52