Sélectionnez des valeurs uniques ou distinctes dans une liste du script shell UNIX


238

J'ai un script ksh qui renvoie une longue liste de valeurs, séparées par des sauts de ligne, et je veux voir uniquement les valeurs uniques / distinctes. Est-il possible de faire cela?

Par exemple, disons que ma sortie est un suffixe de fichier dans un répertoire:

tar
gz
java
gz
java
tar
class
class

Je veux voir une liste comme:

tar
gz
java
class

Réponses:


432

Vous voudrez peut-être regarder les applications uniqet sort.

./yourscript.ksh | trier | uniq

(Pour info, oui, le tri est nécessaire dans cette ligne de commande, uniqne supprime que les lignes en double qui se suivent immédiatement)

ÉDITER:

Contrairement à ce qui a été publié par Aaron Digulla en ce qui concerne uniqles options de ligne de commande de:

Étant donné l'entrée suivante:

classe
pot
pot
pot
poubelle
poubelle
Java

uniq affichera toutes les lignes exactement une fois:

classe
pot
poubelle
Java

uniq -d affichera toutes les lignes qui apparaissent plusieurs fois et les imprimera une fois:

pot
poubelle

uniq -u affichera toutes les lignes qui apparaissent exactement une fois et les imprimera une fois:

classe
Java

2
Juste un info pour les retardataires: @ AaronDigulla a depuis corrigé la réponse.
mklement0

2
très bon point ce "tri est nécessaire dans cette ligne de commande, uniq ne supprime que les lignes en double qui se suivent immédiatement" que je viens d'apprendre !!
HattrickNZ

4
GNU sortpropose également une -uversion pour donner les valeurs uniques.
Arthur2e5

J'ai compris que les uniqcoutures ne traitent que les lignes adjacentes (au moins par défaut), ce qui signifie que l'on peut sortentrer avant l'alimentation uniq.
Stéphane

85
./script.sh | sort -u

C'est la même chose que la réponse du monoxyde , mais un peu plus concise.


6
Vous êtes modeste: votre solution sera également plus performante (probablement uniquement visible avec de grands ensembles de données).
mklement0

Je pense que cela devrait être plus efficace que ... | sort | uniqparce qu'il est exécuté en une seule fois
Adrian Antunez

10

Pour les ensembles de données plus volumineux où le tri peut ne pas être souhaitable, vous pouvez également utiliser le script perl suivant:

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

Cela se souvient simplement de chaque sortie de ligne afin de ne pas la restituer.

Il présente l'avantage sur la " sort | uniq" solution dans la mesure où aucun tri n'est requis à l'avance.


2
Notez que le tri d'un très gros fichier n'est pas un problème en soi avec le tri; il peut trier les fichiers plus volumineux que la RAM + swap disponible. Perl, OTOH, échouera s'il n'y a que quelques doublons.
Aaron Digulla

1
Oui, c'est un compromis en fonction des données attendues. Perl est meilleur pour un énorme ensemble de données avec de nombreux doublons (aucun stockage sur disque requis). Un énorme ensemble de données avec peu de doublons devrait utiliser le tri (et le stockage sur disque). Les petits ensembles de données peuvent utiliser l'un ou l'autre. Personnellement, j'essaierais d'abord Perl, basculerais pour trier s'il échoue.
paxdiablo

Étant donné que le tri ne vous offre un avantage que s'il doit être échangé sur le disque.
paxdiablo

5
C'est super quand je veux la première occurrence de chaque ligne. Le tri romprait cela.
Bluu

10

Avec zsh, vous pouvez le faire:

% 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 vous pouvez utiliser AWK:

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

2
Solutions intelligentes qui n'impliquent pas de trier les entrées. Avertissements: la solution très intelligente mais cryptée awk(voir stackoverflow.com/a/21200722/45375 pour une explication) fonctionnera avec des fichiers volumineux tant que le nombre de lignes uniques est suffisamment petit (car les lignes uniques sont conservées en mémoire ). La zshsolution lit d'abord l'intégralité du fichier en mémoire, ce qui n'est peut-être pas une option avec des fichiers volumineux. De plus, comme écrit, seules les lignes sans espaces incorporés sont gérées correctement; pour résoudre ce problème, utilisez IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}plutôt.
mklement0

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

1
Merci, c'est plus simple (en supposant que vous n'avez pas besoin de définir les variables nécessaires en dehors du sous-shell). Je suis curieux de savoir quand vous avez besoin du [@]suffixe pour référencer tous les éléments d'un tableau - il semble que - au moins à partir de la version 5 - cela fonctionne sans; ou venez-vous de l'ajouter pour plus de clarté?
mklement0

1
@ mklement0, vous avez raison! Je n'y ai pas pensé quand j'ai écrit le post. En fait, cela devrait suffire:print -l "${(fu)$(<infile)}"
Dimitre Radoulov

1
Fantastique, merci d'avoir mis à jour votre message - j'ai pris la liberté de corriger également la awksortie de l' échantillon.
mklement0

9

Les canaliser à travers sortet uniq. Cela supprime tous les doublons.

uniq -dne donne que les doublons, uniq -une donne que les uniques (supprime les doublons).


dois d'abord trier par son apparence
brabster

1
Oui. Ou plus précisément, vous devez regrouper toutes les lignes en double. Cependant, le tri se fait par définition;)
Matthew Scharley

En outre, ce uniq -un'est PAS le comportement par défaut (voir la modification dans ma réponse pour plus de détails)
Matthew Scharley

7

Avec AWK vous pouvez le faire, je le trouve plus rapide que le tri

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

C'est certainement ma façon préférée de faire le travail, merci beaucoup! Surtout pour les fichiers plus volumineux, les solutions sort | uniq ne sont probablement pas ce que vous voulez.
Schmitzi

1

Unique, comme demandé, (mais non trié);
utilise moins de ressources système pour moins de ~ 70 éléments (comme testé avec le temps);
écrit pour prendre l'entrée de stdin,
(ou modifier et inclure dans un autre 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

Je reçois de meilleurs conseils pour obtenir des entrées non dupliquées dans un fichier

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.