Überprüfen Sie, ob das Array in Bash leer ist

110

Ich habe ein Array, das mit verschiedenen Fehlermeldungen gefüllt wird, während mein Skript ausgeführt wird.

Ich brauche eine Möglichkeit, um zu überprüfen, ob es am Ende des Skripts leer ist, und eine bestimmte Aktion auszuführen, wenn dies der Fall ist.

Ich habe bereits versucht, es wie eine normale VAR zu behandeln und es mit -z zu überprüfen, aber das scheint nicht zu funktionieren. Gibt es eine Möglichkeit zu überprüfen, ob ein Array in Bash leer ist oder nicht?

Marcos Sander
quelle

Antworten:

143

Angenommen, Ihr Array ist $errors, überprüfen Sie einfach, ob die Anzahl der Elemente Null ist.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi
Michael Hampton
quelle
10
Bitte beachten Sie, dass dies =ein String-Operator ist. In diesem Fall funktioniert es problemlos, aber ich verwende -eqstattdessen den richtigen arithmetischen Operator (nur für den Fall, dass ich zu -geoder wechseln möchte -ltusw.).
Musiphil
6
Funktioniert nicht mit set -u: "ungebundene Variable" - wenn das Array leer ist.
Igor
@Igor: Funktioniert für mich in Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Wenn ich unset foo, dann druckt es foo: unbound variable, aber das ist anders: Die Array-Variable existiert überhaupt nicht, anstatt zu existieren und leer zu sein.
Peter Cordes
Wird auch in Bash 3.2 (OSX) getestet set -u- solange Sie Ihre Variable zuerst deklariert haben, funktioniert dies einwandfrei.
Zeroimpl
15

In diesem Fall verwende ich in der Regel die arithmetische Erweiterung:

if (( ${#a[@]} )); then
    echo not empty
fi
x-yuri
quelle
Schön und sauber! Ich mag das. Ich stelle auch fest, dass, wenn das erste Element des Arrays immer nicht leer ist, (( ${#a} ))(Länge des ersten Elements) auch funktioniert. Dies wird jedoch fehlschlagen a=(''), wohingegen (( ${#a[@]} ))die Antwort Erfolg haben wird.
cxw
8

Sie können das Array auch als einfache Variable betrachten. Auf diese Weise nur mit

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

oder mit der anderen Seite

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

Das Problem bei dieser Lösung besteht darin , dass , wenn ein Array wie folgt erklärt: array=('' foo). Bei diesen Überprüfungen wird das Array als leer gemeldet, während dies eindeutig nicht der Fall ist. (danke @musiphil!)

Verwenden [ -z "$array[@]" ]ist natürlich auch keine Lösung. Wenn geschweifte Klammern nicht angegeben werden, wird versucht, sie $arrayals Zeichenfolge zu interpretieren ( [@]ist in diesem Fall eine einfache Literalzeichenfolge) und wird daher immer als falsch gemeldet: "Ist die Literalzeichenfolge [@]leer?" Ganz sicher nicht.

wget
quelle
7
[ -z "$array" ]oder [ -n "$array" ]funktioniert nicht. Versuchen Sie es array=('' foo); [ -z "$array" ] && echo empty, und es wird gedruckt empty, obwohl arrayes eindeutig nicht leer ist.
Musiphil
2
[[ -n "${array[*]}" ]]interpoliert das gesamte Array als Zeichenfolge, die Sie auf eine Länge ungleich Null überprüfen. Wenn Sie der Ansicht sind array=("" ""), leer zu sein, anstatt zwei leere Elemente zu haben, kann dies nützlich sein.
Peter Cordes
@ PeterCordes Ich glaube nicht, dass das funktioniert. Der Ausdruck [[ -n " " ]]ergibt ein einzelnes Leerzeichen und ist "wahr", was sehr schade ist. Dein Kommentar ist genau das, was ich will zu tun.
Michael
@Michael: Mist, du hast recht. Es funktioniert nur mit einem 1-Element-Array einer leeren Zeichenfolge, nicht mit 2 Elementen. Ich habe sogar ältere Bashs überprüft und es ist immer noch falsch. wie du sagst set -xzeigt wie es sich ausdehnt. Ich glaube, ich habe diesen Kommentar vor dem Posten nicht getestet. >. <Sie können es zum Laufen bringen, indem IFS=''Sie diese Anweisung festlegen (speichern / wiederherstellen), da die "${array[*]}"Erweiterung Elemente mit dem ersten IFS-Zeichen voneinander trennt. (Oder Leerzeichen, wenn nicht gesetzt). Aber " Wenn IFS null ist, werden die Parameter ohne Trennzeichen verbunden. " (Docs für $ * -Positionsparameter, aber ich nehme dasselbe für Arrays an).
Peter Cordes
@Michael: Fügte eine Antwort hinzu, die dies tut.
Peter Cordes
3

Ich habe es überprüft mit 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

und 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

Im letzteren Fall benötigen Sie folgendes Konstrukt:

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

Damit es bei leeren oder nicht gesetzten Arrays nicht fehlschlägt. Das ist, wenn Sie tun, set -euwie ich es normalerweise tue. Dies sorgt für eine strengere Fehlerprüfung. Aus den Dokumenten :

-e

Beenden Sie sofort, wenn eine Pipeline (siehe Pipelines), die aus einem einzelnen einfachen Befehl (siehe Einfache Befehle), einer Liste (siehe Listen) oder einem zusammengesetzten Befehl (siehe Zusammengesetzte Befehle) bestehen kann, einen Status ungleich Null zurückgibt. Die Shell wird nicht beendet, wenn der fehlgeschlagene Befehl Teil der Befehlsliste ist, die unmittelbar auf ein while- oder until-Schlüsselwort folgt, Teil des Tests in einer if-Anweisung ist, Teil eines Befehls, der in einem && oder || ausgeführt wird liste mit Ausnahme des Befehls, der auf das letzte && oder || folgt, jeden Befehl in einer Pipeline außer dem letzten, oder wenn der Rückgabestatus des Befehls mit! invertiert wird. Wenn ein zusammengesetzter Befehl außer einer Subshell einen Status ungleich Null zurückgibt, weil ein Befehl fehlgeschlagen ist, während -e ignoriert wurde, wird die Shell nicht beendet. Ein Trap auf ERR, falls gesetzt, wird ausgeführt, bevor die Shell beendet wird.

Diese Option gilt für die Shell-Umgebung und jede Subshell-Umgebung separat (siehe Command Execution Environment) und kann dazu führen, dass Subshells beendet werden, bevor alle Befehle in der Subshell ausgeführt werden.

Wenn ein zusammengesetzter Befehl oder eine Shell-Funktion in einem Kontext ausgeführt wird, in dem -e ignoriert wird, ist keiner der Befehle, die innerhalb des zusammengesetzten Befehls oder Funktionskörpers ausgeführt werden, von der Einstellung -e betroffen, selbst wenn -e festgelegt ist und ein Befehl a zurückgibt Fehlerstatus. Wenn ein zusammengesetzter Befehl oder eine Shell-Funktion -e setzt, während sie in einem Kontext ausgeführt wird, in dem -e ignoriert wird, hat diese Einstellung keine Auswirkung, bis der zusammengesetzte Befehl oder der Befehl, der den Funktionsaufruf enthält, abgeschlossen ist.

-u

Behandeln Sie nicht festgelegte Variablen und Parameter außer den Sonderparametern '@' oder '*' als Fehler, wenn Sie eine Parametererweiterung durchführen. Eine Fehlermeldung wird in den Standardfehler geschrieben und eine nicht interaktive Shell wird beendet.

Wenn Sie das nicht benötigen, können Sie einen :+${array[@]}Teil weglassen .

Auch merken Sie , dass es wichtig ist , zu verwenden [[Operator hier, mit [Ihnen zu bekommen:

$ 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
quelle
Mit -usollten Sie tatsächlich nutzen ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski
@ JakubBochenski Über welche Version von Bash sprichst du? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri
Das Problem in den einzelnen Klammern ist @sicherlich das. Sie könnten eine *Array-Erweiterung verwenden [ "${array[*]}" ], oder nicht? Funktioniert trotzdem [[auch prima. Das Verhalten von beiden für ein Array mit mehreren leeren Zeichenfolgen ist ein wenig überraschend. Beide [ ${#array[*]} ]und [[ "${array[@]}" ]]sind falsch für array=()und array=('')aber wahr für array=('' '')(zwei oder mehr leere Zeichenketten). Wenn Sie möchten, dass eine oder mehrere leere Zeichenfolgen wahr sind, können Sie sie verwenden [ ${#array[@]} -gt 0 ]. Wenn du willst, dass sie alle falsch sind, könntest du sie vielleicht //rausholen.
eisd
@eisd Ich könnte es gebrauchen [ "${array[*]}" ], aber wenn ich auf einen solchen Ausdruck stoßen würde, wäre es schwieriger für mich zu verstehen, was er tut. Da [...]arbeitet in Strings das Ergebnis der Interpolation ab. Im Gegensatz zu [[...]], die wissen können, was interpoliert wurde. Das heißt, es kann wissen, dass ein Array übergeben wurde. [[ ${array[@]} ]]liest für mich als "überprüfe, ob das Array nicht leer ist", während [ "${array[*]}" ]als "überprüfe, ob das Ergebnis der Interpolation aller Array-Elemente ein nicht leerer String ist".
X-Yuri
... Was das Verhalten mit zwei leeren Strings betrifft, ist es für mich überhaupt nicht überraschend. Überraschend ist das Verhalten mit einer leeren Zeichenkette. Aber wohl vernünftig. In Bezug auf [ ${#array[*]} ], meinten Sie wahrscheinlich [ "${array[*]}" ], da ersteres für eine beliebige Anzahl von Elementen gilt. Weil die Anzahl der Elemente immer eine nicht leere Zeichenfolge ist. Bei letzterem mit zwei Elementen erweitert sich der Ausdruck in Klammern zu einer ' 'nicht leeren Zeichenfolge. Was [[ ${array[@]} ]]denken sie nur (zu Recht) , daß jede Reihe von zwei Elementen nicht leer ist .
X-Yuri
2

Wenn Sie ein Array mit leeren Elementen erkennen möchten , z. B.arr=("" "") so leer wiearr=()

Sie können alle Elemente zusammenfügen und prüfen, ob das Ergebnis eine Länge von Null hat. (Das Erstellen einer reduzierten Kopie des Array-Inhalts ist für die Leistung nicht ideal, wenn das Array sehr groß sein könnte. Hoffentlich verwenden Sie bash nicht für solche Programme ...)

Erweitert "${arr[*]}"sich jedoch mit Elementen, die durch das erste Zeichen von IFS. Sie müssen also IFS speichern / wiederherstellen und dafür IFS=''sorgen, dass dies funktioniert, oder Sie müssen überprüfen, ob die Zeichenfolgenlänge == # der Array-Elemente - 1 ist. (Ein Array von nElementen verfügt über n-1Trennzeichen.) Um dieses Problem zu lösen, ist es am einfachsten, die Verkettung mit 1 aufzufüllen

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

Testfall mit 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

Leider ist diese nicht für arr=(): [[ 1 -ne 0 ]]. Sie müssen also zuerst separat nach tatsächlich leeren Arrays suchen.


Oder mitIFS='' . Wahrscheinlich möchten Sie IFS speichern / wiederherstellen, anstatt eine Subshell zu verwenden, da Sie mit einer Subshell nicht einfach ein Ergebnis erzielen können.

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

Beispiel:

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

funktioniert mit arr=()- es ist immer noch nur die leere Zeichenfolge.

Peter Cordes
quelle
Ich habe aufgestimmt, aber ich habe angefangen zu verwenden [[ "${arr[*]}" = *[![:space:]]* ]], da ich mich auf mindestens einen Nicht-WS-Charakter verlassen kann.
Michael
@Michael: Ja, das ist eine gute Option, wenn Sie nicht ablehnen müssen arr=(" ").
Peter Cordes
0

In meinem Fall war die zweite Antwort nicht genug, weil es Leerzeichen geben könnte. Ich kam mit:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi
Micha
quelle
echo | wcscheint unnötig ineffizient im Vergleich zu Shell-Built-Ins.
Peter Cordes
Nicht sicher, ob ich @PeterCordes verstehe. Kann ich die zweiten Antworten so ändern, dass [ ${#errors[@]} -eq 0 ];das Leerzeichenproblem umgangen wird ? Ich würde auch den eingebauten bevorzugen.
Micha
Wie genau verursacht Whitespace ein Problem? $#wird zu einer Zahl erweitert und funktioniert auch danach noch einwandfrei opts+=(""). zB unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"und ich bekomme 2. Können Sie ein Beispiel für etwas zeigen, das nicht funktioniert?
Peter Cordes
Es ist lange her. IIRC die Ursprungsquelle druckte immer mindestens "". Für opts = "" oder opts = ("") brauchte ich also 0, nicht 1, und ignorierte die leere Newline oder leere Zeichenfolge.
Micha
Ok, also musst du opts=("")genauso behandeln wie opts=()? Das ist kein leeres Array, aber Sie können mit auf leeres Array oder leeres erstes Element prüfen opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Beachten Sie, dass Ihre aktuelle Antwort "no options" für opts=("" "-foo")lautet, was völlig falsch ist, und dies dieses Verhalten reproduziert. Sie können sich [[ -z "${opts[*]}" ]]vorstellen, alle Array-Elemente in eine flache Zeichenfolge zu interpolieren, die nach einer -zLänge ungleich Null sucht. Wenn die Überprüfung des ersten Elements ausreicht, -z "$opts"funktioniert.
Peter Cordes