Como remover o valor duplicado em um arquivo de texto delimitado por tabulação

5

Eu tenho um texto de coluna delimitado por tabulações, como abaixo

A      B1      B1     C1
B      B2      D2 
C      C12     C13    C13
D      D3      D5      D9
G      F2      F2   

como eu poderia converter a tabela acima, como abaixo

A      B1     C1
B      B2     D2 
C      C12    C13
D      D3     D5     D9
G      F2   

Eu extraí meu arquivo de dados real, é um arquivo delimitado por tabulação e tentei a linha de comando que você (Stéphane Chazelas?) postou funciona bem, mas não foi possível remover a duplicata na última coluna

A  CD274    PDCD1LG2  CD276   PDCD1LG2  CD274
B  NEK2     NEK6      NEK10   NEK10     NEKL-4
C  TNFAIP3  OTUD7B    OTUD7B  TNFAIP3   TNFAIP3
D  DUSP16   DUSP4     DUSP8   VHP-1     DUSP8
E  AGO2     AGO2      AGO2    AGO2      AGO2

a saída precisa ser como abaixo

A  CD274    CD276   PDCD1LG2
B  NEK2     NEK6    NEK10     NEKL-4
C  TNFAIP3  OTUD7B
D  DUSP16   DUSP4   DUSP8     VHP-1
E  AGO2
    
por desu 26.09.2017 / 23:15

7 respostas

7

Primeiro conjunto de dados de exemplo:

$ awk -vOFS='\t' '{ r=""; delete t; for (i=1;i<=NF;++i) { if (!t[$i]++) { r = r ? r OFS $i : $i } } print r }' file
A       B1      C1
B       B2      D2
C       C12     C13
D       D3      D5      D9
G       F2

Segundo conjunto de dados de exemplo (mesmo script awk ):

$ awk -vOFS='\t' '{ r=""; delete t; for (i=1;i<=NF;++i) { if (!t[$i]++) { r = r ? r OFS $i : $i } } print r }' file
A       CD274   PDCD1LG2        CD276
B       NEK2    NEK6    NEK10   NEKL-4
C       TNFAIP3 OTUD7B
D       DUSP16  DUSP4   DUSP8   VHP-1
E       AGO2

O script lê o arquivo de entrada file linha por linha e, para cada linha, passa por cada campo, construindo a linha de saída, r . Se o valor em um campo já tiver sido adicionado à linha de saída (determinado por uma tabela de consulta, t , dos valores de campo usados), o campo será ignorado, caso contrário, será adicionado.

Quando todos os campos de uma linha de entrada foram processados, a linha construída é gerada.

O delimitador do campo de saída está definido para percorrer -vOFS='\t' na linha de comando.

O script awk foi desvendado:

{
    r = ""
    delete t

    for (i = 1; i <= NF; ++i) {
        if (!t[$i]++) {
            r = r ? r OFS $i : $i
        }
    }

    print r
}
    
por 27.09.2017 / 00:54
6

sed / tr, uniq e cole

while read -r l; do sed 's/\t/\n/g' <<< "$l" | uniq | paste -s; done < test

ou compatível com POSIX:

while read -r l; do echo "$l" | tr '\t' '\n' | uniq | paste -s -; done < test

Para o arquivo test , isso linha por linha substituirá todos os caracteres de Tab por quebras de linha, execute uniq para excluir dupes e substitua as quebras de linha por caracteres Tab novamente .

$ cat test
A       B1      B1      C1
B       B2      D2
C       C12     C13     C13
D       D3      D5      D9
G       F2      F2

$ while read -r l; do sed 's/\t/\n/g' <<< "$l" | uniq | paste -s; done < test
A       B1      C1
B       B2      D2
C       C12     C13
D       D3      D5      D9
G       F2

NB : esta solução não funcionará para duplicatas em várias linhas, por exemplo, C1 em

A       B1      B1      C1
C1      B       B2      D2
    
por 26.09.2017 / 23:26
6

Talvez algo como:

gawk -vRS='\s*\S*' -vORS= '{$0=RT};$1!=prev;{prev=$1}'

O truque RS=pattern ... {$0=RT} permite processar registros definidos como as partes que correspondem ao padrão.

Então, aqui, estamos dividindo a entrada em <whitespace><non-whitespace> $0 records, <non-whitespace> em $1 (o primeiro e único campo). Estamos imprimindo os registros cujo $1 não é igual ao anterior.

Em uma entrada como:

A      B1      B1     C1
B      B2      D2 
C      C12     C13    C13
D      D3      D5      D9
G      F2      F2

Os registros são:

[A][      B1][      B1][     C1][
B][      B2][      D2][ 
C][      C12][     C13][    C13][
D][      D3][      D5][      D9][
G][      F2][      F2][
]

Não funciona no seu segundo exemplo e note que ele pode remover alguns caracteres de nova linha.

    
por 26.09.2017 / 23:34
2

Esta é mais uma solução de desafio de golfe de código / freak:

xargs -L1 -I{} echo '; {}' < ./test.txt | \
      xargs -n1 | \
      uniq | \
      xargs | \
      sed -e 's/; /\n/g' -e 's/ \+/\t/g'

Mas evita usar loops e todas as outras máquinas pesadas vistas em outras respostas.

Também se baseia no pressuposto de que seus dados não contêm ; character.

    
por 27.09.2017 / 09:08
1

com perl :

palavras únicas em cada linha:

perl -MList::Util=uniq -lape '$_ = join "\t", uniq @F'

palavras únicas globalmente:

perl -lape '$_ = join "\t", grep {!$count{$_}++} @F'

Ou para considerar apenas as palavras de cada linha começando com a 2 nd uma:

perl -lape '$_ = join "\t", shift(@F), grep {!$count{$_}++} @F'
    
por 27.09.2017 / 12:08
0

Com bash v4.3 (se você não se importar com a ordem dos campos conforme ele é classificado, exceto primeiro)

while IFS='\n' read -r line; 
    do aline=( $line );
    echo ${aline[0]} $(sort -u <(printf "%s\n" ${aline[@]:1}));
done < infile

Explicação:

  • aline=( $line ) isto faz a linha salvar em um array 'aline'
  • ${aline[0]} imprime o primeiro elemento de uma matriz 'aline' (o índice da matriz está começando com zero em bash )
  • printf "%s\n" ${aline[@]:1} imprime cada elemento da matriz 'aline' em linhas separadas e ignora o primeiro elemento; Então
  • sort -u classifica cada linha e remove as entradas duplicadas
  • echo isso também combina elementos de linha divididos após classificar em um linear.

    Por favor, veja o exemplo abaixo para ter uma melhor visão deste passo:

    printf "C\n4\nB\nC" |sort -u 
    4
    B
    C
    echo $(printf "C\n4\nB\nC" |sort -u)
    4 B C
    

Isso fornecerá a saída como:

A CD274 CD276 PDCD1LG2
B NEK10 NEK2 NEK6 NEKL-4
C OTUD7B TNFAIP3
D DUSP16 DUSP4 DUSP8 VHP-1
E AGO2
    
por 27.09.2017 / 12:08
0

substituição de sed com referência de volta

sed -re 's/\s+$//; s/(\t[^\t]+)+$//'

( s/\s+$// se livra do espaço em branco à direita, como em seu exemplo.)

    
por 27.09.2017 / 13:36