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

8

Si j'ai une chaîne "1 2 3 2 1" - ou un tableau [1,2,3,2,1] - comment puis-je sélectionner les valeurs uniques, c'est-à-dire

"1 2 3 2 1" produces "1 2 3" 

ou

[1,2,3,2,1] produces [1,2,3]

Similaire à uniq mais uniq semble fonctionner sur des lignes entières, pas sur des motifs au sein d'une ligne ...

Michael Durrant
la source

Réponses:

4

Avec GNU awk(cela conserve également l'ordre d'origine)

printf '%s\n' "1 2 3 2 1" | awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}'
1 2 3 

Vers readdans un bashtableau

read -ra arr<<<$(printf '%s\n' "1 2 3 2 1" |
 awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}')
printf "%s\n"  "${arr[@]}"
1
2
3
iruvar
la source
Comment puis-je en faire un tableau?
Michael Durrant
@MichaelDurrant, si vous voulez dire un bashtableau, a ajouté un moyen
iruvar
Regardez ici si votre tableau contient des espaces
Tom Hale
@iruvar pouvez-vous expliquer ce que cela signifie? Je suis nouveau dans les scripts awk et il serait utile que vous puissiez clarifier ce qui se passe réellement lorsque vous dites ceci! a [$ 0] ++
Abhishek
@iruvar s'il n'est pas possible d'expliquer dans les commentaires tout site Web expliquant au moins la syntaxe ci-dessus serait bénéfique.
Abhishek
9

Si vous utilisez zsh:

$ array=(1 2 3 2 1)
$ echo ${(u)array[@]}
1 2 3

ou (si l' KSH_ARRAYSoption n'est pas définie) même

$ echo ${(u)array}
1 2 3
jimmij
la source
1
Si le tableau peut contenir des éléments vides, vous devez utiliser "${(u)array[@]}"ou à la "${(@u)array}"place (notez les guillemets).
Stéphane Chazelas
J'utilise zsh 5.1.1 (x86_64-ubuntu-linux-gnu) , et ${(u)array}fonctionne même si le tableau est vide ou contient une chaîne vide, sans guillemets.
kiamlaluno
4

Pour un tableau avec des valeurs arbitraires, c'est assez délicat bashcar il n'a pas d'opérateur intégré pour cela.

bash Cependant, il ne prend pas en charge le stockage de caractères NUL dans ses variables, vous pouvez donc vous en servir pour passer cela à d'autres commandes:

L'équivalent de zsh's:

new_array=("${(@u}array}")

sur un système GNU récent, pourrait être:

eval "new_array=($(
  printf "%s\0" "${array[@]}" |
    LC_ALL=C sort -zu |
    xargs -r0 bash -c 'printf "%q\n" "$@"' sh
  ))"

Alternativement, avec des versions récentes de bash, et en supposant qu'aucun des éléments du tableau n'est vide, vous pouvez utiliser des tableaux associatifs:

unset hash
typeset -A hash
for i in "${array[@]}"; do
  hash[$i]=
done
new_array=("${!hash[@]}")

Avec bash 4.4 et plus récent et avec GNU sort:

readarray -td '' new_array < <(
  printf '%s\0' "${array[@]}" | LC_ALL=C sort -zu)

L'ordre des éléments ne serait pas le même dans ces différentes solutions.

Avec tcsh:

set -f new_array = ($array:q)

Conserverait le f élément irst ( a b a=> a b) comme zshdes » (u)drapeau d'extension.

set -l new_array = ($array:q)

Conserverait le dernier ( a b a=> b a). Cependant, ceux-ci suppriment les éléments vides du tableau.

Stéphane Chazelas
la source
1

Cette solution a fonctionné pour moi.

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

Ce qui précède produit 1 2 3 comme sortie.

La version plus courte suggérée par Costas pourrait être,

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

Pour stocker les résultats finaux dans un tableau, vous pouvez faire quelque chose comme,

IFS=$' '
arr=($(printf "%s\n" "${ids[@]}" | sort -u | tr '\n' ' '))
unset IFS

Maintenant, quand je fais un écho arr, c'est la sortie que j'obtiens.

echo "${arr[@]}"
1 2 3

Références

https://stackoverflow.com/a/13648438/1742825 https://stackoverflow.com/a/9449633/1742825

Ramesh
la source
@Costas, merci. Je l'ai intégré à la réponse.
Ramesh
Comment puis-je faire du résultat final un tableau?
Michael Durrant
@MichaelDurrant, veuillez voir la réponse mise à jour et faites-moi savoir si cela va bien.
Ramesh
Si vous voulez mettre le résultat dans le tableau, vous pouvez supprimer la dernière commandetr '\n' ' '
Costas
0

Pour le faire entièrement dans le shell et mettre le résultat dans un tableau,

declare -A seen
for word in one two three two one
do
        if [ ! "${seen[$word]}" ]
        then
                result+=("$word")
                seen[$word]=1
        fi
done
echo "${result[@]}"

En mots: si nous n'avons pas encore vu un mot donné, ajoutez-le au resulttableau et marquez-le comme ayant été vu. Une fois qu'un mot a été vu, ignorez ses apparitions ultérieures.

Scott
la source
2
Notez que vous avez besoin unset seenavant declare -A seendans le cas où a $seenété précédemment défini (même en tant que variable scalaire de l'environnement).
Stéphane Chazelas