Vérifier si le tableau est vide dans Bash

110

J'ai un tableau qui est rempli avec différents messages d'erreur pendant l'exécution de mon script.

J'ai besoin d'un moyen de vérifier si c'est vide ou non à la fin du script et de prendre une action spécifique si c'est le cas.

J'ai déjà essayé de le traiter comme un revendeur à valeur normale et d'utiliser -z pour le vérifier, mais cela ne semble pas fonctionner. Existe-t-il un moyen de vérifier si un tableau est vide ou non dans Bash?

Marcos Sander
la source

Réponses:

143

En supposant que votre tableau soit $errors, vérifiez simplement si le nombre d'éléments est égal à zéro.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi
Michael Hampton
la source
10
Veuillez noter qu'il =s'agit d'un opérateur de chaîne. Il se trouve que cela fonctionne bien dans ce cas, mais j'utiliserais -eqplutôt l' opérateur arithmétique approprié (juste au cas où je voudrais passer à -geou -lt, etc.).
musiphil
6
Ne fonctionne pas avec set -u: "variable non liée" - si le tableau est vide.
Igor
@ Igor: Fonctionne pour moi dans Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Si I unset foo, alors cela s’imprime foo: unbound variable, mais c’est différent: la variable tableau n’existe pas du tout, elle existe et est vide.
Peter Cordes
Également testé dans Bash 3.2 (OSX) lors de l'utilisation set -u- tant que vous avez déclaré votre variable en premier, cela fonctionne parfaitement.
zeroimpl
15

J'utilise généralement l'expansion arithmétique dans ce cas:

if (( ${#a[@]} )); then
    echo not empty
fi
x-yuri
la source
Agréable et propre! Je l'aime. Je note également que si le premier élément du tableau est toujours non vide, (( ${#a} ))(longueur du premier élément) fonctionnera également. Cependant, cela échouera a=(''), alors que (( ${#a[@]} ))donné dans la réponse réussira.
cxw
8

Vous pouvez également considérer le tableau comme une simple variable. De cette façon, juste en utilisant

if [ -z "$array" ]; then
    echo "Array empty"
else
    echo "Array non empty"
fi

ou en utilisant l'autre côté

if [ -n "$array" ]; then
    echo "Array non empty"
else
    echo "Array empty"
fi

Le problème avec cette solution est que si un tableau est déclaré comme ceci: array=('' foo). Ces vérifications indiqueront que le tableau est vide, alors que ce n'est clairement pas le cas. (merci @musiphil!)

L'utilisation [ -z "$array[@]" ]n'est clairement pas une solution non plus. Ne pas spécifier les accolades essaie d'interpréter $arraycomme une chaîne (il [@]s'agit dans ce cas d'une chaîne littérale simple) et est donc toujours signalé comme étant faux: "la chaîne littérale est-elle [@]vide?" Clairement pas.

wget
la source
7
[ -z "$array" ]ou [ -n "$array" ]ne fonctionne pas. Essayez array=('' foo); [ -z "$array" ] && echo empty, et il imprimera emptymême s'il arrayn'est clairement pas vide.
musiphil
2
[[ -n "${array[*]}" ]]interpole l'intégralité du tableau en tant que chaîne, que vous vérifiez pour une longueur non nulle. Si vous considérez array=("" "")être vide, plutôt que d'avoir deux éléments vides, cela peut être utile.
Peter Cordes
@ PeterCordes Je ne pense pas que cela fonctionne. L'expression est évaluée à un seul caractère d'espacement et [[ -n " " ]]est "vraie", ce qui est dommage. Votre commentaire est exactement ce que je veux faire.
Michael
@ Michael: Merde, vous avez raison. Cela fonctionne uniquement avec un tableau à 1 élément d'une chaîne vide, pas 2 éléments. J'ai même vérifié l'ancienne bash et c'est toujours faux là-bas; comme tu dis set -xmontre comment il se développe. Je suppose que je n'ai pas testé ce commentaire avant de poster. >. <Vous pouvez le faire fonctionner en définissant IFS=''(enregistrez / restaurez-le autour de cette instruction), car le "${array[*]}"développement sépare les éléments avec le premier caractère de IFS. (Ou espace si non défini). Mais " Si IFS est nul, les paramètres sont joints sans séparateurs intermédiaires " (docs pour $ * positional params, mais je suppose même pour les tableaux).
Peter Cordes
@ Michael: Ajout d'une réponse qui fait cela.
Peter Cordes
3

J'ai vérifié avec bash-4.4.0:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]} ]]; then
        echo not empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

et bash-4.1.5:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]:+${array[@]}} ]]; then
        echo non-empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

Dans ce dernier cas, vous avez besoin de la construction suivante:

${array[@]:+${array[@]}}

pour qu'il n'échoue pas sur un tableau vide ou non défini. C'est si tu fais set -eucomme je le fais d'habitude. Cela permet une vérification d'erreur plus stricte. De la docs :

-e

Quittez immédiatement si un pipeline (voir Pipelines), qui peut consister en une seule commande (voir Commandes simples), une liste (voir Listes) ou une commande composée (voir Commandes composées) renvoie un statut différent de zéro. Le shell ne se ferme pas si la commande qui échoue fait partie de la liste de commandes suivant immédiatement un mot-clé while ou Until, une partie du test dans une instruction if, une partie d'une commande exécutée dans un && ou || liste sauf la commande suivant le && ou || final, toutes les commandes d'un pipeline sauf le dernier, ou si l'état de retour de la commande est inversé avec!. Si une commande composée autre qu'un sous-shell renvoie un statut différent de zéro car une commande a échoué alors que l'option -e était ignorée, le shell ne se ferme pas. Une interruption sur ERR, si définie, est exécutée avant la sortie du shell.

Cette option s'applique à l'environnement shell et à chaque environnement de sous-shell séparément (voir Environnement d'exécution des commandes), et peut entraîner la fermeture des sous-shell avant l'exécution de toutes les commandes du sous-shell.

Si une commande composée ou une fonction shell s'exécute dans un contexte où -e est ignoré, aucune des commandes exécutées dans la commande composée ou le corps de la fonction n'est affectée par le paramètre -e, même si -e est défini et qu'une commande renvoie un état d'échec. Si une commande composée ou une fonction shell définit -e lors de l'exécution dans un contexte où -e est ignoré, ce paramètre n'aura aucun effet tant que la commande composée ou la commande contenant l'appel de fonction ne sera pas terminée.

-u

Traitez les variables non définies et les paramètres autres que les paramètres spéciaux «@» ou «*» comme une erreur lors de l’expansion des paramètres. Un message d'erreur sera écrit sur l'erreur standard et un shell non interactif se fermera.

Si vous n'en avez pas besoin, n'hésitez pas à en omettre une :+${array[@]}partie.

Notez également qu'il est essentiel d'utiliser [[operator ici, avec [vous obtenez:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
array=(a b c d)
if [ "${array[@]}" ]; then
    echo non-empty
else
    echo empty
fi

$ ./1.sh
_/1.sh: line 4: [: too many arguments
empty
x-yuri
la source
Avec -uvous devriez réellement utiliser ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski le
@JakubBochenski De quelle version de bash parlez-vous? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri
Le problème dans l'exemple des crochets simples est @sûrement le. Vous pouvez utiliser l' *extension de tableau comme [ "${array[*]}" ], n'est-ce pas? Pourtant, [[fonctionne aussi très bien. Le comportement de ces deux éléments pour un tableau avec plusieurs chaînes vides est un peu surprenant. Les deux [ ${#array[*]} ]et [[ "${array[@]}" ]]sont faux pour array=()et array=('')mais vrais pour array=('' '')(deux chaînes vides ou plus). Si vous voulez une ou plusieurs chaînes vides pour que toutes donnent la valeur true, vous pouvez utiliser [ ${#array[@]} -gt 0 ]. Si vous les vouliez tous faux, vous pourriez peut-être //les faire sortir.
eisd
@ eisd je pourrais utiliser [ "${array[*]}" ], mais si je rencontrais une telle expression, il serait plus difficile pour moi de comprendre ce que cela fait. Depuis [...]fonctionne en termes de chaînes sur le résultat de l'interpolation. Par opposition à [[...]], qui peut être conscient de ce qui a été interpolé. C'est-à-dire qu'il peut savoir qu'il a été passé un tableau. [[ ${array[@]} ]]me lit comme "vérifier si le tableau est non vide", tandis que [ "${array[*]}" ]comme "vérifier si le résultat de l'interpolation de tous les éléments du tableau est une chaîne non vide".
x-yuri
... En ce qui concerne le comportement avec deux chaînes vides, cela ne me surprend pas du tout. Ce qui est surprenant, c'est le comportement avec une chaîne vide. Mais discutablement raisonnable. En ce qui concerne [ ${#array[*]} ], vous avez probablement voulu dire [ "${array[*]}" ], puisque le premier est vrai pour un nombre quelconque d’éléments. Parce que le nombre d'éléments est toujours une chaîne non vide. En ce qui concerne ce dernier avec deux éléments, l'expression entre crochets ' 'est complétée par une chaîne non vide. En ce qui concerne [[ ${array[@]} ]], ils pensent simplement (et à juste titre) que tout tableau de deux éléments est non vide.
x-yuri
2

Si vous voulez détecter un tableau avec des éléments vides , commearr=("" "") vide, identique àarr=()

Vous pouvez coller tous les éléments ensemble et vérifier si le résultat est zéro. (Construire une copie aplatie du contenu du tableau n'est pas idéal pour la performance si le tableau peut être très grand. Mais j'espère que vous n'utilisez pas bash pour de tels programmes ...)

Mais se "${arr[*]}"développe avec des éléments séparés par le premier caractère de IFS. Donc, vous devez enregistrer / restaurer IFS et le faire IFS=''pour que cela fonctionne, ou bien vérifiez que la longueur de la chaîne == # d'éléments de tableau - 1. (Un tableau d' néléments a des n-1séparateurs). Pour régler ce problème, il est plus facile d’enrichir la concaténation de 1

arr=("" "")

## Assuming default non-empty IFS
## TODO: also check for ${#arr[@]} -eq 0
concat="${arr[*]} "      # n-1 separators + 1 space + array elements
[[ "${#concat}" -ne "${#arr[@]}" ]]  && echo not empty array || echo empty array

cas de test avec set -x

### a non-empty element
$ arr=("" "x")
  + arr=("" "x")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat=' x '
  + [[ 3 -ne 2 ]]
  + echo not empty array
not empty array

### 2 empty elements
$ arr=("" "")
  + arr=("" "")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat='  '
  + [[ 2 -ne 2 ]]
  + echo empty array
empty array

Malheureusement , cela ne fonctionne pas pour arr=(): [[ 1 -ne 0 ]]. Il est donc nécessaire de vérifier séparément les tableaux réellement vides.


Ou avecIFS='' . Vous voudrez probablement sauvegarder / restaurer IFS au lieu d'utiliser un sous-shell, car vous ne pouvez pas obtenir facilement le résultat d'un sous-shell.

# inside a () subshell so we don't modify our own IFS
(IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)

exemple:

$ arr=("" "")
$ (IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)
   + IFS=
   + [[ -n '' ]]
   + echo empty array
empty array

ne fonctionne avec arr=()- il est encore juste la chaîne vide.

Peter Cordes
la source
J'ai voté, mais j'ai commencé à utiliser [[ "${arr[*]}" = *[![:space:]]* ]], car je peux compter sur au moins un caractère non WS.
Michael
@ Michael: oui, c'est une bonne option si vous n'avez pas besoin de rejeter arr=(" ").
Peter Cordes
0

Dans mon cas, la deuxième réponse ne suffisait pas, car il pouvait y avoir des espaces. Je suis venu avec:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi
Micha
la source
echo | wcsemble inutilement inefficace comparé à l’utilisation de shell intégrés.
Peter Cordes
Vous ne savez pas si je comprends @PeterCordes, puis-je modifier la deuxième réponse [ ${#errors[@]} -eq 0 ];afin de résoudre le problème des espaces? Je préférerais aussi le intégré.
Micha
Comment les espaces posent-ils un problème? $#étend à un nombre, et fonctionne bien même après opts+=(""). par exemple unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"et je reçois 2. Pouvez-vous montrer un exemple de quelque chose qui ne fonctionne pas?
Peter Cordes
Il y a longtemps. IIRC la source d'origine a toujours imprimé au moins "". Ainsi, pour opts = "" ou opts = (""), j'avais besoin de 0, et non de 1, en ignorant la nouvelle ligne ou la chaîne vide.
Micha
Ok, alors vous devez traiter opts=("")le même opts=()? Ce n'est pas un tableau vide, mais vous pouvez rechercher un tableau vide ou un premier élément vide avec opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Notez que votre réponse actuelle dit "pas d'options" pour opts=("" "-foo"), ce qui est totalement faux, et cela reproduit ce comportement. Vous pourriez, [[ -z "${opts[*]}" ]]je suppose, interpoler tous les éléments du tableau en une chaîne plate qui -zvérifie la longueur non nulle. Si cocher le premier élément est suffisant, -z "$opts"fonctionne.
Peter Cordes