Controlla se l'array è vuoto in Bash


110

Ho un array che viene riempito con diversi messaggi di errore durante l'esecuzione del mio script.

Ho bisogno di un modo per verificare se è vuoto o non alla fine dello script e, se lo è, intraprendere un'azione specifica.

Ho già provato a trattarlo come un normale VAR e usando -z per controllarlo, ma non sembra funzionare. C'è un modo per verificare se un array è vuoto o no in Bash?

Risposte:


143

Supponendo che l'array sia $errors, basta controllare per vedere se il conteggio degli elementi è zero.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi

10
Si noti che =è un operatore di stringa. In questo caso funziona bene, ma in -eqalternativa utilizzerei l'operatore aritmetico corretto (nel caso in cui volessi passare a -geo -lt, ecc.).
musiphil

6
Non funziona con set -u: "variabile non associata" - se l'array è vuoto.
Igor

@Igor: Funziona per me in Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Se io unset foo, quindi stampa foo: unbound variable, ma è diverso: la variabile array non esiste affatto, piuttosto che esistente ed essere vuota.
Peter Cordes,

Testato anche in Bash 3.2 (OSX) durante l'utilizzo set -u- purché tu abbia dichiarato prima la tua variabile, questo funziona perfettamente.
zeroimpl

15

In genere utilizzo l'espansione aritmetica in questo caso:

if (( ${#a[@]} )); then
    echo not empty
fi

Bello e pulito! Mi piace. Noto anche che se il primo elemento dell'array è sempre non vuoto, (( ${#a} ))(lunghezza del primo elemento) funzionerà anche. Tuttavia, ciò fallirà a=(''), mentre (( ${#a[@]} ))la risposta fornita avrà successo.
Cxw

8

Puoi anche considerare l'array come una semplice variabile. In questo modo, basta usare

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

o usando l'altro lato

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

Il problema con questa soluzione è che se una matrice è dichiarato in questo modo: array=('' foo). Questi controlli segnaleranno l'array come vuoto, mentre chiaramente non lo è. (grazie @musiphil!)

L'uso non [ -z "$array[@]" ]è chiaramente una soluzione né. Non specificando le parentesi graffe si cerca di interpretare $arraycome una stringa ( [@]è in quel caso una semplice stringa letterale) e viene quindi sempre indicata come falsa: "la stringa letterale è [@]vuota?" Chiaramente no.


7
[ -z "$array" ]o [ -n "$array" ]non funziona. Prova array=('' foo); [ -z "$array" ] && echo emptye stamperà emptyanche se arraychiaramente non è vuoto.
musiphil,

2
[[ -n "${array[*]}" ]]interpola l'intero array come una stringa, che si controlla per lunghezza diversa da zero. Se ritieni array=("" "")di essere vuoto, anziché avere due elementi vuoti, questo potrebbe essere utile.
Peter Cordes,

@PeterCordes Non penso che funzioni. L'espressione restituisce un singolo carattere spaziale ed [[ -n " " ]]è "vera", il che è un peccato. Il tuo commento è esattamente quello che voglio fare.
Michael,

@Michael: Merda, hai ragione. Funziona solo con un array di 1 elemento di una stringa vuota, non con 2 elementi. Ho anche controllato bash più vecchio ed è ancora sbagliato lì; come dici tu set -xmostra come si espande. Immagino di non aver testato quel commento prima di pubblicare. >. <Puoi farlo funzionare impostando IFS=''(salvalo / ripristinalo attorno a questa istruzione), poiché l' "${array[*]}"espansione separa gli elementi con il primo carattere di IFS. (O spazio se non impostato). Ma " Se IFS è nullo, i parametri vengono uniti senza separatori intermedi. " (Documenti per parametri posizionali $ *, ma presumo lo stesso per gli array).
Peter Cordes,

@Michael: aggiunta una risposta per farlo.
Peter Cordes,

3

L'ho controllato con 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

e 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

In quest'ultimo caso è necessario il seguente costrutto:

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

affinché non fallisca sull'array vuoto o non impostato. Questo è se fai set -eucome faccio di solito. Ciò consente un controllo degli errori più rigoroso. Dai documenti :

-e

Esci immediatamente se una pipeline (vedi Pipeline), che può consistere in un singolo comando semplice (vedi Comandi semplici), un elenco (vedi Elenchi) o un comando composto (vedi Comandi composti) restituisce uno stato diverso da zero. La shell non si chiude se il comando che fallisce fa parte dell'elenco dei comandi immediatamente dopo un po 'o fino alla parola chiave, parte del test in un'istruzione if, parte di qualsiasi comando eseguito in un && o || list tranne il comando che segue l'ultimo && o ||, qualsiasi comando in una pipeline tranne l'ultimo, o se lo stato di ritorno del comando viene invertito con!. Se un comando composto diverso da una subshell restituisce uno stato diverso da zero perché un comando ha avuto esito negativo mentre -e veniva ignorato, la shell non esce. Una trap su ERR, se impostata, viene eseguita prima della chiusura della shell.

Questa opzione si applica all'ambiente shell e a ciascun ambiente subshell separatamente (consultare Ambiente di esecuzione comandi) e può causare la chiusura delle subshell prima di eseguire tutti i comandi nella subshell.

Se un comando composto o una funzione shell viene eseguita in un contesto in cui -e viene ignorato, nessuno dei comandi eseguiti all'interno del comando composto o del corpo della funzione sarà influenzato dall'impostazione -e, anche se -e è impostato e un comando restituisce un stato di errore. Se un comando composto o una funzione shell imposta -e durante l'esecuzione in un contesto in cui -e viene ignorato, tale impostazione non avrà alcun effetto fino al completamento del comando composto o del comando contenente la chiamata di funzione.

-u

Considera variabili e parametri non impostati diversi dai parametri speciali '@' o '*' come un errore quando si esegue l'espansione dei parametri. Verrà scritto un messaggio di errore nell'errore standard e uscirà una shell non interattiva.

Se non ti serve, sentiti libero di omettere la :+${array[@]}parte.

Nota anche che è essenziale utilizzare l' [[operatore qui, con [te ottieni:

$ 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

Con -ute dovresti effettivamente usare ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski,

@JakubBochenski Di quale versione di bash stai parlando? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri

Il problema nell'esempio tra parentesi singole è @sicuramente il. Potresti usare l' *espansione dell'array come [ "${array[*]}" ], vero? Comunque, [[funziona anche bene. Il comportamento di entrambi questi per un array con più stringhe vuote è un po 'sorprendente. Entrambi [ ${#array[*]} ]e [[ "${array[@]}" ]]sono falsi per array=()e array=('')ma veri per array=('' '')(due o più stringhe vuote). Se volessi che una o più stringhe vuote fornissero tutte vere, potresti usare [ ${#array[@]} -gt 0 ]. Se li volessi tutti falsi, potresti forse //eliminarli.
eisd

Potrei usare @eisd [ "${array[*]}" ], ma se dovessi imbattermi in una tale espressione, sarebbe più difficile per me capire cosa fa. Dal momento che [...]opera in termini di stringhe sul risultato dell'interpolazione. Al contrario [[...]], che può essere consapevole di ciò che è stato interpolato. Cioè, può sapere che è stato passato un array. [[ ${array[@]} ]]mi legge come "controlla se l'array non è vuoto", mentre [ "${array[*]}" ]come "controlla se il risultato dell'interpolazione di tutti gli elementi dell'array è una stringa non vuota".
x-yuri,

... Per quanto riguarda il comportamento con due stringhe vuote, non mi sorprende affatto. Ciò che sorprende è il comportamento con una stringa vuota. Ma probabilmente ragionevole. Per quanto riguarda [ ${#array[*]} ], probabilmente intendevi [ "${array[*]}" ], poiché il primo è vero per qualsiasi numero di elementi. Perché il numero di elementi è sempre una stringa non vuota. Riguardo a quest'ultimo con due elementi, l'espressione racchiusa tra parentesi si espande alla ' 'quale è stringa non vuota. Per quanto riguarda [[ ${array[@]} ]], pensano semplicemente (e giustamente) che qualsiasi array di due elementi non sia vuoto.
x-yuri,

2

Se si desidera rilevare un array con elementi vuoti , comearr=("" "") vuoto, uguale aarr=()

Puoi incollare tutti gli elementi insieme e verificare se il risultato è di lunghezza zero. (Costruire una copia appiattita del contenuto dell'array non è l'ideale per le prestazioni se l'array potrebbe essere molto grande. Ma si spera che non si usi bash per programmi del genere ...)

Ma si "${arr[*]}"espande con elementi separati dal primo carattere di IFS. Quindi è necessario salvare / ripristinare IFS e farlo IFS=''per farlo funzionare, oppure verificare che la lunghezza della stringa == # di elementi dell'array - 1. (Un array di nelementi ha n-1separatori). Per far fronte a quello off-by-one, è più facile riempire la concatenazione di 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

test case con 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

Purtroppo questo non riesce per arr=(): [[ 1 -ne 0 ]]. Quindi dovresti controllare prima gli array effettivamente vuoti separatamente.


O conIFS='' . Probabilmente vorresti salvare / ripristinare IFS invece di usare una subshell, perché non puoi ottenere facilmente un risultato da una subshell.

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

esempio:

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

non funziona con arr=()- è ancora solo una stringa vuota.


Ho effettuato l'upgrade, ma ho iniziato a utilizzare [[ "${arr[*]}" = *[![:space:]]* ]], poiché posso contare su almeno un personaggio non WS.
Michael,

@Michael: sì, questa è una buona opzione se non hai bisogno di rifiutare arr=(" ").
Peter Cordes,

0

Nel mio caso, la seconda risposta non è stata sufficiente perché potrebbero esserci spazi bianchi. Sono venuto con:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi

echo | wcsembra inutilmente inefficiente rispetto all'uso della shell integrata.
Peter Cordes,

Non sono sicuro che se capisco @PeterCordes, posso modificare le seconde risposte ' [ ${#errors[@]} -eq 0 ];in modo da aggirare il problema degli spazi bianchi? Preferirei anche il built-in.
Micha,

In che modo esattamente gli spazi bianchi causano un problema? $#si espande in un numero e funziona bene anche dopo opts+=(""). ad es. unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"e ottengo 2. Puoi mostrare un esempio di qualcosa che non funziona?
Peter Cordes,

È molto tempo fa. IIRC la fonte di origine sempre stampata almeno "". Pertanto, per opts = "" o opts = ("") avevo bisogno di 0, non di 1, ignorando la riga vuota o la stringa vuota.
Micha,

Ok, quindi devi trattare opts=("")lo stesso di opts=()? Questo non è un array vuoto, ma è possibile verificare la presenza di un array vuoto o di un primo elemento vuoto con opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Nota che la tua risposta attuale dice "nessuna opzione" per opts=("" "-foo"), che è totalmente falso, e questo riproduce quel comportamento. Si potrebbe [[ -z "${opts[*]}" ]]Credo, interpolare tutti gli elementi dell'array in una stringa piatta, che -zverifica la presenza di lunghezza diversa da zero. Se il controllo del primo elemento è sufficiente, -z "$opts"funziona.
Peter Cordes,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.