GNU/Linux >> Tutoriels Linux >  >> Linux

Comment puis-je obtenir des valeurs uniques à partir d'un tableau dans Bash ?

Je me rends compte que cela a déjà été répondu, mais il est apparu assez haut dans les résultats de recherche, et cela pourrait aider quelqu'un.

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

Exemple :

~> 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
~>

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

Ensuite, pour obtenir un tableau trié (et unique), nous devons le convertir dans un format que le tri comprend et pouvoir le reconvertir en éléments de tableau bash. C'est le meilleur que j'ai trouvé :

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 de 1 élément vide (car printf avait 0 argument mais s'imprime toujours comme s'il avait un argument vide - voir l'explication). Vous devez donc saisir cela dans un si ou quelque chose.

Explication :Le format %q pour printf "shell échappe" à l'argument imprimé, de la même manière que bash peut récupérer quelque chose comme eval ! Parce que chaque élément est imprimé avec échappement shell 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, analysant les valeurs échappées en texte littéral.

ex.

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

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


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

$ 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 car dans n'importe quel tableau (associatif ou traditionnel, dans n'importe quelle langue), chaque clé ne peut apparaître qu'une seule fois. Lorsque le for la boucle arrive à la seconde valeur de aa en a[2] , il écrase b[aa] qui a été défini à l'origine pour a[0] .

Faire des choses dans bash natif peut être plus rapide que d'utiliser des canaux et des outils externes comme sort et uniq , bien que pour les ensembles de données plus volumineux, 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 le for boucle en utilisant printf la capacité de recycler son format pour plusieurs arguments, bien que cela semble nécessiter eval . (Arrêtez de lire maintenant si cela vous convient.)

$ 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 du tableau sont déterminées avant le fractionnement des mots. Cela signifie que la sortie de la substitution de commande est considérée comme un seul mot plutôt qu'un ensemble de paires clé=valeur.

Bien que cela utilise un sous-shell, il utilise uniquement les commandes intégrées bash pour traiter les valeurs du tableau. Assurez-vous d'évaluer votre utilisation de eval d'un œil critique. Si vous n'êtes pas sûr à 100 % que chepner ou glenn jackman ou greycat ne trouveront rien à redire à votre code, utilisez plutôt la boucle for.


Un peu hacky, mais ça devrait le faire :

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

Pour enregistrer les résultats uniques triés dans un tableau, effectuez une affectation de tableau :

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

Si votre shell prend en charge les chaînes ici (bash devrait), vous pouvez épargner un echo processus en le modifiant en :

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

Remarque en date du 28 août 2021 :

Selon ShellCheck wiki 2207 un read -a pipe doit être utilisé pour éviter le fractionnement. Ainsi, dans bash, la commande serait :

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

ou

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

Saisie :

ids=(aa ab aa ac aa ad)

Sortie :

aa ab ac ad

Explication :

  • "${ids[@]}" - Syntaxe pour travailler avec des tableaux shell, qu'ils soient utilisés dans le cadre de echo ou un herestring. Le @ partie signifie "tous les éléments du tableau"
  • tr ' ' '\n' - Convertir tous les espaces en nouvelles lignes. Parce que votre tableau est vu par shell comme des éléments sur une seule ligne, séparés par des espaces; et parce que le tri s'attend à ce que l'entrée soit sur des lignes séparées.
  • sort -u - trier et conserver uniquement les éléments uniques
  • tr '\n' ' ' - reconvertir les retours à la ligne que nous avons ajoutés précédemment en espaces.
  • $(...) - Substitution de commandes
  • À part :tr ' ' '\n' <<< "${ids[@]}" est une façon plus efficace de faire :echo "${ids[@]}" | tr ' ' '\n'

Linux
  1. Comment créer un tableau d'éléments uniques à partir d'une chaîne/tableau dans Bash ?

  2. Comment obtenir un masque de réseau à partir de bash ?

  3. comment décaler la valeur du tableau dans bash

  4. Comment puis-je obtenir un binaire à partir d'un fichier .py

  5. Comment obtenir le nom de l'utilisateur à partir de l'uid

Bash break :comment sortir d'une boucle

Comment puis-je *uniquement* obtenir le nombre d'octets disponibles sur un disque en bash ?

Comment puis-je exécuter un exécutable Windows à partir de WSL (Ubuntu) Bash

Comment puis-je obtenir le dernier numéro de la chaîne dans bash?

Comment puis-je filtrer les résultats uniques de la sortie grep ?

Comment puis-je obtenir la longueur d'un fichier vidéo à partir de la console ?