136 votes

Expansion d'un tableau vide en Bash avec `set -u`

J'écris un script bash script qui possède set -u et j'ai un problème avec l'expansion des tableaux vides : bash semble traiter un tableau vide comme une variable non définie pendant l'expansion :

$ set -u
$ arr=()
$ echo "foo: '${arr[@]}'"
bash: arr[@]: unbound variable

( declare -a arr n'aide pas non plus.)

Une solution courante consiste à utiliser ${arr[@]-} à la place, substituant ainsi une chaîne vide au lieu d'un tableau vide ("non défini"). Cependant, ce n'est pas une bonne solution, puisque maintenant vous ne pouvez pas faire la différence entre un tableau contenant une seule chaîne vide et un tableau vide. (@-expansion est spécial en bash, il développe "${arr[@]}" en "${arr[0]}" "${arr[1]}" … ce qui en fait un outil parfait pour construire des lignes de commande).

$ countArgs() { echo $#; }
$ countArgs a b c
3
$ countArgs
0
$ countArgs ""
1
$ brr=("")
$ countArgs "${brr[@]}"
1
$ countArgs "${arr[@]-}"
1
$ countArgs "${arr[@]}"
bash: arr[@]: unbound variable
$ set +u
$ countArgs "${arr[@]}"
0

Existe-t-il un moyen de contourner ce problème, autre que la vérification de la longueur d'un tableau dans un fichier if (voir l'exemple de code ci-dessous), ou de désactiver la fonction -u pour cette courte pièce ?

if [ "${#arr[@]}" = 0 ]; then
   veryLongCommandLine
else
   veryLongCommandLine "${arr[@]}"
fi

Mise à jour : Supprimé bugs tag dû à l'explication de ikegami.

96voto

ikegami Points 133140

Selon la documentation,

Une variable de tableau est considérée comme définie si une valeur a été attribuée à un indice. La chaîne de caractères null est une valeur valide.

Aucune valeur n'a été attribuée à l'indice, le tableau n'est donc pas défini.

Cependant, alors que la documentation suggère qu'une erreur est appropriée dans ce cas, ce n'est plus le cas depuis 4.4 .

$ bash --version | head -n 1
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)

$ set -u

$ arr=()

$ echo "foo: '${arr[@]}'"
foo: ''

Il existe une conditionnelle que vous pouvez utiliser en ligne pour obtenir ce que vous souhaitez dans les anciennes versions : Utiliser ${arr[@]+"${arr[@]}"} au lieu de "${arr[@]}" .

$ function args { perl -E'say 0+@ARGV; say "$_: $ARGV[$_]" for 0..$#ARGV' -- "$@" ; }

$ set -u

$ arr=()

$ args "${arr[@]}"
-bash: arr[@]: unbound variable

$ args ${arr[@]+"${arr[@]}"}
0

$ arr=("")

$ args ${arr[@]+"${arr[@]}"}
1
0: 

$ arr=(a b c)

$ args ${arr[@]+"${arr[@]}"}
3
0: a
1: b
2: c

Testé avec bash 4.2.25 et 4.3.11.

68voto

dimo414 Points 7128

En seulement L'idiome sûr est ${arr[@]+"${arr[@]}"}

A moins que vous ne vous intéressiez qu'à Bash 4.4+, mais vous ne seriez pas en train de répondre à cette question si c'était le cas :)

C'est déjà la recommandation de la Réponse de ikegami mais il y a beaucoup d'informations erronées et de suppositions dans ce fil. D'autres modèles, tels que ${arr[@]-} o ${arr[@]:0} sont no sûr dans toutes les versions majeures de Bash.

Comme le montre le tableau ci-dessous, la seule expansion qui soit fiable dans toutes les versions modernes de Bash est la suivante ${arr[@]+"${arr[@]}"} (colonne +" ). Il convient de noter que plusieurs autres expansions échouent dans Bash 4.2, y compris (malheureusement) la plus courte, celle de ${arr[@]:0} qui ne se contente pas de produire un résultat incorrect, mais qui échoue. Si vous devez prendre en charge des versions antérieures à la 4.4, et en particulier à la 4.2, c'est la seule façon de procéder.

Screenshot of different idioms across versions

Malheureusement, d'autres + Les expansions qui, à première vue, se ressemblent, ont en fait un comportement différent. L'utilisation de :+ au lieu de + ( :+" dans le tableau), par exemple, ne fonctionne pas parce que : -expansion traite un tableau contenant un seul élément vide ( ('') ) comme "null" et n'aboutit donc pas (systématiquement) au même résultat.

En citant l'expansion complète au lieu du tableau imbriqué ( "${arr[@]+${arr[@]}}" , "+ dans le tableau), dont je me serais attendu à ce qu'elle soit à peu près équivalente, n'est pas sûre non plus en 4.2.

Vous pouvez voir le code qui a généré ces données ainsi que les résultats pour plusieurs versions supplémentaires de bash dans la section ce gist .

25voto

ijs Points 251

La réponse acceptée de @ikegami est subtilement erronée ! L'incantation correcte est ${arr[@]+"${arr[@]}"} :

$ countArgs () { echo "$#"; }
$ arr=('')
$ countArgs "${arr[@]:+${arr[@]}}"
0   # WRONG
$ countArgs ${arr[@]+"${arr[@]}"}
1   # RIGHT
$ arr=()
$ countArgs ${arr[@]+"${arr[@]}"}
0   # Let's make sure it still works for the other case...

17voto

agg3l Points 894

Il s'avère que la gestion des tableaux a été modifiée dans la version récente (2016/09/16) de bash 4.4 (disponible dans Debian stretch, par exemple).

$ bash --version | head -n1
bash --version | head -n1
GNU bash, version 4.4.0(1)-release (x86_64-pc-linux-gnu)

Désormais, l'expansion des tableaux vides n'émet plus d'avertissement

$ set -u
$ arr=()
$ echo "${arr[@]}"

$ # everything is fine

16voto

Jayen Points 643

Ceci peut être une autre option pour ceux qui préfèrent ne pas dupliquer arr[@] et qui sont d'accord pour avoir une chaîne vide

echo "foo: '${arr[@]:-}'"

à tester :

set -u
arr=()
echo a "${arr[@]:-}" b # note two spaces between a and b
for f in a "${arr[@]:-}" b; do echo $f; done # note blank line between a and b
arr=(1 2)
echo a "${arr[@]:-}" b
for f in a "${arr[@]:-}" b; do echo $f; done

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