Existe uma maneira de 'uniq' por coluna?


195

Eu tenho um arquivo .csv como este:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Eu tenho que remover e-mails duplicados (a linha inteira) do arquivo (ou seja, uma das linhas que contém overflow@example.como exemplo acima). Como uso uniqapenas no campo 1 (separado por vírgulas)? Segundo man, uniqnão tem opções para colunas.

Eu tentei algo com sort | uniqmas não funciona.

Respostas:


325
sort -u -t, -k1,1 file
  • -u para único
  • -t, então vírgula é o delimitador
  • -k1,1 para o campo-chave 1

Resultado do teste:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
isso não funciona se a coluna contém em si vírgula (com citações)
user775187

13
por que você precisa de 1 em -k1,1? por que não apenas -k1?
Hello_there_andy

18
@hello_there_andy: Isso é explicado no manual ( man sort). Representa a posição inicial e final.
Serrano

3
@CarlSmotricz: Eu testei e confirmou o que sort's manpage diz: ' com , cheque de ordenação estrita, sem , saída apenas o primeiro de um igual prazo .' Portanto, é de fato "a primeira ocorrência da duplicata antes da classificação". -u--unique-c-c
Geremia

2
isso muda a ordem das linhas também, não é?
rkachach

102
awk -F"," '!_[$1]++' file
  • -F define o separador de campos.
  • $1 é o primeiro campo.
  • _[val]procura valno hash _(uma variável regular).
  • ++ incrementar e retornar valor antigo.
  • ! retorna não lógico.
  • há uma impressão implícita no final.

4
Esta abordagem é duas vezes mais rápido que tipo
Bitek

9
Isso também tem o benefício adicional de manter as linhas na ordem original!
AffluentOwl

8
Se você precisar do último uniq em vez do primeiro, este script do awk ajudará:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima

3
@eshwar basta adicionar mais campos ao índice do dicionário! Por exemplo, !_[$1][$2]++pode ser usado para classificar pelos dois primeiros campos. Meu awk-fu não é forte o suficiente para ser capaz de ser único em vários campos. :(
Soham Chowdhury

1
Brilhante! esta opção é melhor do que a resposta porque mantém as linhas de ordem
rkachach

16

Para considerar várias colunas.

Classifique e forneça uma lista exclusiva com base nas colunas 1 e 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : dois pontos é separador
  • -k 1,1 -k 3,3 com base nas colunas 1 e 3

8

ou se você quiser usar o uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

dá:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
Gostaria de apontar uma possível simplificação: você pode despejar o cat! Em vez de canalizar para tr, deixe tr ler o arquivo usando <. A tubulação caté uma complicação desnecessária comum usada por iniciantes. Para grandes quantidades de dados, há um efeito no desempenho.
Carl Smotricz

4
Bom saber. THX! (Claro que isso faz sentido, pensando de "gato" e "preguiça";))
Carsten C.

A reversão de campos pode ser simplificada com rev.
Hielke Walinga 9/07/19

5

Se você quiser reter a última duplicata, poderá usar

 tac a.csv | sort -u -t, -r -k1,1 |tac

Qual foi a minha exigência

aqui

tac irá reverter o arquivo linha por linha


1

Aqui está uma maneira muito bacana.

Primeiro formate o conteúdo de modo que a coluna a ser comparada para exclusividade tenha uma largura fixa. Uma maneira de fazer isso é usar o awk printf com um especificador de largura de campo / coluna ("% 15s").

Agora, as opções -f e -w do uniq podem ser usadas para pular os campos / colunas anteriores e especificar a largura de comparação (largura da (s) coluna (s)).

Aqui estão três exemplos.

No primeiro exemplo ...

1) Transforme temporariamente a coluna de interesse em uma largura fixa maior ou igual à largura máxima do campo.

2) Use a opção -f uniq para ignorar as colunas anteriores e use a opção -w uniq para limitar a largura ao tmp_fixed_width.

3) Remova os espaços finais da coluna para "restaurar" sua largura (assumindo que não havia espaços finais anteriormente).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

No segundo exemplo ...

Crie uma nova coluna uniq 1. Em seguida, remova-a após a aplicação do filtro uniq.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

O terceiro exemplo é o mesmo que o segundo, mas para várias colunas.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

bem, mais simples do que isolar a coluna com o awk, se você precisar remover tudo com um certo valor para um determinado arquivo, por que não fazer grep -v:

por exemplo, para excluir tudo com o valor "col2" na segunda linha: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Se isso não for bom o suficiente, porque algumas linhas podem ser removidas incorretamente, possivelmente com o valor correspondente sendo exibido em uma coluna diferente, você pode fazer algo assim:

awk para isolar a coluna incorreta: por exemplo

awk -F, '{print $2 "|" $line}'

o -F define o campo delimitado como ",", $ 2 significa coluna 2, seguida por algum delimitador personalizado e, em seguida, a linha inteira. Você pode filtrar removendo as linhas que começam com o valor incorreto:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

e depois retire o material antes do delimitador:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(note: o comando sed é desleixado porque não inclui valores de escape. Além disso, o padrão sed deve realmente ser algo como "[^ |] +" (ou seja, qualquer coisa que não seja o delimitador). Mas esperamos que isso esteja claro o suficiente.


3
Ele não deseja limpar as linhas, ele deseja manter uma única cópia de uma linha com uma sequência específica. Uniq é o caso de uso correto.
ingyhere

-3

Classificando o arquivo sortprimeiro, você pode aplicar uniq.

Parece classificar o arquivo muito bem:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Você também pode fazer alguma mágica do AWK:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

Isso não é exclusivo por coluna, conforme solicitado na pergunta. Isso é único para toda a linha. Além disso, você não precisa fazer uma classificação para fazer um uniq. Os dois são mutualmente exclusivos.
Javid Jamae

1
Sim você está certo. O último exemplo faz o que a pergunta pediu, embora a resposta aceita seja muito mais limpa. Em relação sort, portanto uniq, sortprecisa ser feito antes de fazer, uniqcaso contrário, não funciona (mas você pode pular o segundo comando e apenas usar sort -u). From uniq(1): "Filtre as linhas correspondentes adjacentes de INPUT (ou entrada padrão), gravando em OUTPUT (ou saída padrão)."
Mikael S

Ah, você está certo sobre a classificação antes da uniq. Eu nunca percebi que o uniq só funciona em linhas adjacentes. Eu acho que sempre uso apenas sort -u.
Javid Jamae 25/09
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.