Selecione valores únicos ou distintos de uma lista no script de shell UNIX


238

Eu tenho um script ksh que retorna uma longa lista de valores separados por nova linha e quero ver apenas os valores exclusivos / distintos. É possível fazer isso?

Por exemplo, digamos que minha saída seja sufixos de arquivo em um diretório:

tar
gz
java
gz
java
tar
class
class

Eu quero ver uma lista como:

tar
gz
java
class

Respostas:


432

Você pode querer olhar para os aplicativos uniqe sort.

./yourscript.ksh | classificar | uniq

(Para sua informação, sim, a classificação é necessária nesta linha de comando, uniqapenas retira as linhas duplicadas imediatamente após a outra)

EDITAR:

Ao contrário do que foi postado por Aaron Digulla em relação às uniqopções de linha de comando:

Dada a seguinte entrada:

classe
jarra
jarra
jarra
bin
bin
Java

uniq produzirá todas as linhas exatamente uma vez:

classe
jarra
bin
Java

uniq -d produzirá todas as linhas que aparecerem mais de uma vez e as imprimirá uma vez:

jarra
bin

uniq -u produzirá todas as linhas que aparecerem exatamente uma vez e as imprimirá uma vez:

classe
Java

2
Apenas um FYI para retardatários: a resposta de @ AaronDigulla foi corrigida.
usar o seguinte comando

2
ponto muito bom esse `tipo é necessário nesta linha de comando, o uniq apenas remove linhas duplicadas que são imediatamente uma após a outra`, que acabei de aprender !!
HattrickNZ

4
O GNU também sortpossui uma -uversão para fornecer valores únicos.
precisa saber é o seguinte

Eu descobri que as uniqcosturas processam apenas linhas adjacentes (pelo menos por padrão), o que significa que se pode sortinserir antes da alimentação uniq.
Stphane 19/02/16

85
./script.sh | sort -u

É o mesmo que a resposta do monóxido , mas um pouco mais concisa.


6
Você está sendo modesto: sua solução também terá um desempenho melhor (provavelmente visível apenas com grandes conjuntos de dados).
precisa saber é o seguinte

Eu acho que deveria ser mais eficiente do que ... | sort | uniquma vez que é realizada em um único tiro
Adrian Antunez

10

Para conjuntos de dados maiores em que a classificação pode não ser desejável, você também pode usar o seguinte script perl:

./yourscript.ksh | perl -ne 'if (!defined $x{$_}) { print $_; $x{$_} = 1; }'

Basicamente, isso apenas lembra todas as saídas de linha, para que não sejam exibidas novamente.

Tem a vantagem sobre a sort | uniqsolução " ", pois não há necessidade de classificação antecipada.


2
Observe que a classificação de um arquivo muito grande não é um problema per se com a classificação; ele pode classificar arquivos maiores que a RAM + swap disponível. Perl, OTOH, falhará se houver apenas algumas duplicatas.
Aaron Digulla 6/03/09

1
Sim, é uma troca dependendo dos dados esperados. Perl é melhor para grandes conjuntos de dados com muitas duplicatas (não é necessário armazenamento baseado em disco). Um conjunto de dados enorme com poucas duplicatas deve usar classificação (e armazenamento em disco). Conjuntos de dados pequenos também podem usar. Pessoalmente, eu tentaria primeiro o Perl, alternaria para classificar se falhar.
22420

Como a classificação só oferece um benefício se for necessário trocar para o disco.
paxdiablo

5
Isso é ótimo quando quero a primeira ocorrência de cada linha. A classificação quebraria isso.
Bluu

10

Com o zsh, você pode fazer isso:

% cat infile 
tar
more than one word
gz
java
gz
java
tar
class
class
zsh-5.0.0[t]% print -l "${(fu)$(<infile)}"
tar
more than one word
gz
java
class

Ou você pode usar o AWK:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class

2
Soluções inteligentes que não envolvem a classificação da entrada. Advertências: A awksolução muito inteligente, porém enigmática (consulte stackoverflow.com/a/21200722/45375 para obter uma explicação) funcionará com arquivos grandes, desde que o número de linhas exclusivas seja pequeno o suficiente (pois as linhas exclusivas são mantidas na memória ) A zshsolução lê o arquivo inteiro primeiro na memória, o que pode não ser uma opção para arquivos grandes. Além disso, conforme escrito, apenas as linhas sem espaços incorporados são tratadas corretamente; Para corrigir isso, use em IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}vez disso.
precisa saber é o seguinte

Corrigir. Ou:(IFS=$'\n' u=($(<infile)); print -l "${(u)u[@]}")
Dimitre Radoulov

1
Obrigado, isso é mais simples (supondo que você não precise definir variáveis ​​necessárias fora do subshell). Estou curioso para saber quando você precisa do [@]sufixo para referenciar todos os elementos de uma matriz - parece que - pelo menos a partir da versão 5 - funciona sem ela; ou você acabou de adicioná-lo para maior clareza?
precisa saber é o seguinte

1
@ mklement0, você está certo! Eu não pensei nisso quando escrevi o post. Na verdade, isso deve ser suficiente:print -l "${(fu)$(<infile)}"
Dimitre Radoulov

1
Fantástico, obrigado por atualizar sua postagem - tomei a liberdade de corrigir também a awksaída de amostra.
precisa saber é o seguinte

9

Conduza-os através de sorte uniq. Isso remove todas as duplicatas.

uniq -dfornece apenas as duplicatas, uniq -ufornece apenas as únicas (retira as duplicatas).


Conseguiu classificar pela primeira vez pelos olhares dele
brabster

1
Sim você faz. Ou, mais precisamente, você precisa agrupar todas as linhas duplicadas. Sorting faz isso, por definição, embora;)
Matthew Scharley

Além disso, uniq -unão é o comportamento padrão (consulte a edição em minha resposta para mais detalhes)
Matthew Scharley

7

Com o AWK você pode fazer, acho mais rápido que o tipo

 ./yourscript.ksh | awk '!a[$0]++'

Essa é definitivamente a minha maneira favorita de fazer o trabalho, muito obrigado! Especialmente para arquivos maiores, o tipo | uniq-solutions provavelmente não é o que você deseja.
Schmitzi

1

Exclusivo, conforme solicitado, (mas não classificado);
usa menos recursos do sistema para menos de ~ 70 elementos (conforme testado com o tempo);
escrito para receber a entrada de stdin,
(ou modificar e incluir em outro script):
(Bash)

bag2set () {
    # Reduce a_bag to a_set.
    local -i i j n=${#a_bag[@]}
    for ((i=0; i < n; i++)); do
        if [[ -n ${a_bag[i]} ]]; then
            a_set[i]=${a_bag[i]}
            a_bag[i]=$'\0'
            for ((j=i+1; j < n; j++)); do
                [[ ${a_set[i]} == ${a_bag[j]} ]] && a_bag[j]=$'\0'
            done
        fi
    done
}
declare -a a_bag=() a_set=()
stdin="$(</dev/stdin)"
declare -i i=0
for e in $stdin; do
    a_bag[i]=$e
    i=$i+1
done
bag2set
echo "${a_set[@]}"

0

Recebo dicas melhores para obter entradas não duplicadas em um arquivo

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
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.