117 votes

Comment obtenir des valeurs uniques d'un tableau en Bash ?

J'ai presque la même question que ici .

J'ai un tableau qui contient aa ab aa ac aa ad etc. Maintenant, je veux sélectionner tous les éléments uniques de ce tableau. Je pensais que ce serait simple avec sort | uniq ou avec sort -u comme ils l'ont mentionné dans cette autre question, mais rien n'a changé dans le tableau... Le code est le suivant :

echo `echo "${ids[@]}" | sort | uniq`

Qu'est-ce que je fais de mal ?

164voto

sampson-chen Points 13413

C'est un peu compliqué, mais ça devrait le faire :

echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '

Pour enregistrer les résultats uniques triés dans un tableau, procédez comme suit Assignation d'un tableau :

sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

Si votre shell supporte herestrings ( bash devrait), vous pouvez épargner un echo en le transformant en :

tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '

Une note en date du 28 août 2021 :

Selon ShellCheck wiki 2207 a read -a doit être utilisé pour éviter les fissures. Ainsi, en bash la commande serait :

IFS=" " read -r -a ids <<< "$(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"

o

IFS=" " read -r -a ids <<< "$(tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' ')"

Entrée :

ids=(aa ab aa ac aa ad)

Sortie :

aa ab ac ad

Explication :

  • "${ids[@]}" - Syntaxe permettant de travailler avec des tableaux shell, qu'ils soient utilisés dans le cadre de echo ou un herestring. Le site @ Part signifie "tous les éléments du tableau".
  • tr ' ' '\n' - Convertit tous les espaces en nouvelles lignes. Parce que votre tableau est vu par le shell comme des éléments sur une seule ligne, séparés par des espaces ; et parce que sort s'attend à ce que les entrées soient sur des lignes séparées.
  • sort -u - trier et ne conserver que les éléments uniques
  • tr '\n' ' ' - convertir les nouvelles lignes que nous avons ajoutées plus tôt en espaces.
  • $(...) - Substitution de commande
  • A part : tr ' ' '\n' <<< "${ids[@]}" est une façon plus efficace de procéder : echo "${ids[@]}" | tr ' ' '\n'

39voto

ghoti Points 14996

Si vous utilisez Bash version 4 ou supérieure (ce qui devrait être le cas dans toute version moderne de Linux), vous pouvez obtenir des valeurs de tableau uniques dans bash en créant un nouveau tableau associatif qui contient chacune des valeurs du tableau d'origine. Quelque chose comme ceci :

$ a=(aa ac aa ad "ac ad")
$ declare -A b
$ for i in "${a[@]}"; do b["$i"]=1; done
$ printf '%s\n' "${!b[@]}"
ac ad
ac
aa
ad

Cela fonctionne parce que dans tout tableau (associatif ou traditionnel, dans n'importe quel langage), chaque clé ne peut apparaître qu'une seule fois. Lorsque la clé for La boucle arrive à la deuxième valeur de aa sur a[2] il écrase b[aa] qui était initialement prévu pour a[0] .

Faire des choses en bash natif peut être plus rapide que d'utiliser des pipes et des outils externes comme sort y uniq Cependant, pour les grands ensembles de données, vous obtiendrez probablement de meilleures performances si vous utilisez un langage plus puissant comme awk, python, etc.

Si vous vous sentez en confiance, vous pouvez éviter les for en utilisant printf La capacité de l'entreprise à recycler son format pour de multiples arguments, bien que cela semble nécessiter eval . (Arrêtez de lire maintenant si vous êtes d'accord avec ça).

$ eval b=( $(printf ' ["%s"]=1' "${a[@]}") )
$ declare -p b
declare -A b=(["ac ad"]="1" [ac]="1" [aa]="1" [ad]="1" )

La raison pour laquelle cette solution nécessite eval est que les valeurs des tableaux sont déterminées avant le découpage des mots. Cela signifie que la sortie de la commande substitution est considérée comme un seul mot plutôt qu'un ensemble de paires clé=valeur.

Bien qu'elle utilise un sous-shell, elle n'utilise que des builtins bash pour traiter les valeurs du tableau. Assurez-vous d'évaluer votre utilisation de eval avec un œil critique. Si vous n'êtes pas sûr à 100% que Chepner, Glenn Jackman ou Greycat ne trouveront aucune faille dans votre code, utilisez plutôt la boucle for.

28voto

das.cyklone Points 361

Je me rends compte qu'on a déjà répondu à cette question, mais elle est apparue assez haut dans les résultats de recherche, et elle pourrait aider quelqu'un.

printf "%s\n" "${IDS[@]}" | sort -u

Ejemplo:

~> IDS=( "aa" "ab" "aa" "ac" "aa" "ad" )
~> echo  "${IDS[@]}"
aa ab aa ac aa ad
~>
~> printf "%s\n" "${IDS[@]}" | sort -u
aa
ab
ac
ad
~> UNIQ_IDS=($(printf "%s\n" "${IDS[@]}" | sort -u))
~> echo "${UNIQ_IDS[@]}"
aa ab ac ad
~>

17voto

vontrapp Points 79

Si les éléments de votre tableau comportent des espaces blancs ou tout autre caractère spécial de l'interpréteur de commandes (et pouvez-vous être sûr qu'ils n'en comportent pas ?), pour les capturer avant tout (et vous devriez toujours le faire), mettez votre tableau entre guillemets ! "${a[@]}" . Bash interprétera littéralement ceci comme "chaque élément du tableau dans un fichier séparé". argument ". Dans bash, cela fonctionne toujours, toujours.

Ensuite, pour obtenir un tableau trié (et unique), nous devons le convertir dans un format que sort comprend et être capable de le reconvertir en éléments de tableau bash. C'est la meilleure solution que j'ai trouvée :

eval a=($(printf "%q\n" "${a[@]}" | sort -u))

Malheureusement, cela échoue dans le cas particulier du tableau vide, transformant le tableau vide en un tableau d'un élément vide (parce que printf avait 0 argument mais imprime toujours comme s'il avait un argument vide - voir l'explication). Donc vous devez attraper cela dans un if ou autre.

Explication : Le format %q pour printf "échappe à l'obus" l'argument imprimé, de la même manière que bash peut le récupérer dans quelque chose comme eval ! Parce que chaque élément est imprimé sur sa propre ligne, le seul séparateur entre les éléments est la nouvelle ligne, et l'affectation du tableau prend chaque ligne comme un élément, en analysant les valeurs échappées en texte littéral.

par exemple

> a=("foo bar" baz)
> printf "%q\n" "${a[@]}"
'foo bar'
baz
> printf "%q\n"
''

L'évaluation est nécessaire pour enlever l'échappement de chaque valeur retournant dans le tableau.

13voto

corbyn42 Points 131

Sort" peut être utilisé pour ordonner la sortie d'une boucle for :

for i in ${ids[@]}; do echo $i; done | sort

et éliminer les doublons avec "-u" :

for i in ${ids[@]}; do echo $i; done | sort -u

Enfin, vous pouvez simplement écraser votre tableau avec les éléments uniques :

ids=( `for i in ${ids[@]}; do echo $i; done | sort -u` )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X