Ü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?

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

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

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.


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

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.


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

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.