Aqui está meu conjunto de dados:
col1,col2,col3
a,b,c
a,d,f
d,u,v
f,g,h
d,u,g
x,t,k
Resultado esperado:
f,g,h
x,t,k
Critério de seleção:
Se algo ocorrer col1
várias vezes, todas as linhas associadas serão excluídas.
Posso resolver isso usando Linux sort
ou uniq
qualquer outra coisa?
Responder1
Aqui está uma abordagem de duas passagens "sem buffer" (1)awk
(funcionará apenas em arquivos regulares).
awk -F',' 'NR==FNR{cnt[$1]++;next} FNR>1&&cnt[$1]==1' input.csv input.csv
Isso processará o arquivo duas vezes, portanto, ele será declarado duas vezes como argumento na linha de comando.
- O argumento
-F','
define o separador de campos como,
. - Na primeira passagem, quando
NR
, o contador de linhas global, é igual aFNR
, o contador de linhas por arquivo, registramos com que frequência cada valor na coluna 1 é encontrado em um arraycnt
(que assume o valor como "índice do array"), mas pule imediatamente o processamento para a próxima linha. - Na segunda passagem, verificamos se o contador de ocorrências do valor atual da primeira coluna é exatamente 1 e se o número da linha dentro do arquivo é maior que 1 (para pular o cabeçalho). Somente se isso for verdade a linha atual será impressa. Isso faz uso da
awk
sintaxe de uma expressão fora dos blocos de regras que avalia paratrue
instruirawk
a imprimir a linha atual.
(1) Em reação a um comentário que fizsem bufferentre aspas, pois como a solução irá armazenar alguns dados do arquivo temporariamente na RAM, elafazvem com uso de RAM. No entanto, não armazenará o conteúdo do arquivo literalmentealém dissopara quaisquer outros dados de manutenção de rolagem na RAM (queEUconsideraria "buffering" no sentido real).
Responder2
Supondo que o arquivo seja, /tmp/data
você pode fazer isso com uma linha perl:
perl -e 'while(<STDIN>) { /(^\S+?),/; $show->{$1}=$_; $count->{$1}++;}; foreach(keys %$show) {print $show->{$_} if($count->{$_} == 1);}' < /tmp/data
Ou mais legível...:
while(<STDIN>) { #loop through all lines in the input and put the lines in "$_"
/(^\S+?),/; #Everything before the first "," now ends up in "$1"
$show->{$1} = $_; #a hash will be created with as keys the "$1" and as values the "$_"
$count->{$1}++; #In the hash $count the number of occurrences will be increased everytime the same $1 appears
}
foreach(keys %$show) { #loop trough all lines
print $show->{$_} if($count->{$_} == 1); #only print them if they occur once
}
Responder3
awk
única solução
não mantendo a ordem
awk -F, 'NR>1 { count[$1]++ ; line[$1]=$0 ;} END { for ( c in count) if (count[c] ==1) print line[c]}' data
mantendo a ordem
awk -F, 'NR>1 { row[a]=$0; col[a]=$1; count[$1]++; ++a; } END { for (i=0; i<a; ++i) if (count[col[i]]==1) print row[i]; }' data
onde
-F,
diga ao awk para usar,
como separadorNR>1
depois da primeira linhacount[$1]++
contar elemento da primeira colunaline[$1]=$0
linha de lojaEND
após o final do arquivofor ( c in count)
percorrer o elementoif (count[c] ==1)
se apenas umprint line[c]
linha de impressãoa
ecol[]
são usados para armazenar a ordem da linha para preservar a variante.
isso pode ser onelined, eu dobro para facilitar a leitura
Responder4
decorar/classificar/usar/desdecorar usando qualquer versão das ferramentas POSIX obrigatórias e quaisquer caracteres em sua entrada (a menos que sua entrada seja realmente um CSV com campos entre aspas que podem conter vírgulas e/ou novas linhas, mas todas as outras respostas também falhariam) e mantendo a ordem das linhas de entrada para a saída e abrindo a entrada apenas uma vez, para que funcione se a entrada vier de um canal ou arquivo e sem armazenar toda a entrada na memória:
$ awk 'BEGIN{FS=OFS=","} NR>1{print ++cnt[$1], NR, $0}' file |
sort -nt, -k1,1r -k2,2 |
awk -F, '(!seen[$3]++) && ($1==1)' |
cut -d, -f3-
f,g,h
x,t,k