Sprawdź, czy tablica jest pusta w Bash

110

Mam tablicę, która wypełnia się różnymi komunikatami o błędach podczas działania mojego skryptu.

Potrzebuję sposobu, aby sprawdzić, czy nie jest pusty na końcu skryptu i podjąć konkretne działanie, jeśli tak jest.

Próbowałem już traktować to jak normalną VAR i używać -z, aby to sprawdzić, ale to nie działa. Czy istnieje sposób sprawdzenia, czy tablica jest pusta, czy nie w Bash?

Marcos Sander
źródło

Odpowiedzi:

143

Załóżmy, że tablica jest $errors, po prostu sprawdź, czy liczba elementów wynosi zero.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi
Michael Hampton
źródło
10
Należy pamiętać, że =jest to operator łańcuchowy. Zdarza się, że w tym przypadku działa dobrze, ale -eqzamiast tego użyłbym właściwego operatora arytmetycznego (na wypadek, gdyby chciałem przełączyć na -gelub -lt, itp.).
musiphil
6
Nie działa z set -u: „zmienną niezwiązaną” - jeśli tablica jest pusta.
Igor
@Igor: Działa dla mnie w Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Jeśli ja unset foo, to drukuje foo: unbound variable, ale jest inaczej: zmienna tablicowa w ogóle nie istnieje, a nie istnieje i jest pusta.
Peter Cordes,
Testowany również w wersji Bash 3.2 (OSX) podczas używania set -u- dopóki zadeklarujesz swoją zmienną jako pierwszą, działa to doskonale.
zeroimpl
15

Zwykle używam rozszerzenia arytmetycznego w tym przypadku:

if (( ${#a[@]} )); then
    echo not empty
fi
x-yuri
źródło
Ładnie i czysto! Lubię to. Zauważam również, że jeśli pierwszy element tablicy jest zawsze niepusty, (( ${#a} ))(długość pierwszego elementu) również będzie działać. To się jednak nie powiedzie a=(''), a odpowiedź (( ${#a[@]} ))podana w odpowiedzi się powiedzie.
cxw
8

Możesz także rozważyć tablicę jako prostą zmienną. W ten sposób po prostu używam

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

lub używając drugiej strony

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

Problem z tego rozwiązania jest to, że jeśli tablica jest zadeklarowana następująco: array=('' foo). Te kontrole zgłoszą tablicę jako pustą, podczas gdy wyraźnie nie jest. (dzięki @musiphil!)

Używanie nie [ -z "$array[@]" ]jest oczywiście rozwiązaniem. Brak określenia nawiasów klamrowych próbuje interpretować $arrayjako ciąg znaków ( [@]w takim przypadku jest to zwykły ciąg literałów) i dlatego jest zawsze zgłaszany jako fałsz: „czy ciąg literałów jest [@]pusty?” Najwyraźniej nie.

wget
źródło
7
[ -z "$array" ]lub [ -n "$array" ]nie działa. Spróbuj array=('' foo); [ -z "$array" ] && echo empty, a wydrukuje się, emptymimo że arraywyraźnie nie jest pusty.
musiphil
2
[[ -n "${array[*]}" ]]interpoluje całą tablicę jako ciąg, który sprawdza się pod kątem niezerowej długości. Jeśli uważasz, że array=("" "")jest pusty, zamiast mieć dwa puste elementy, może to być przydatne.
Peter Cordes,
@PeterCordes Nie sądzę, że to działa. Wyrażenie ocenia się na pojedynczy znak spacji i [[ -n " " ]]jest „prawdziwe”, a szkoda. Twój komentarz jest dokładnie tym, co chcę zrobić.
Michael
@Michael: Cholera, masz rację. Działa tylko z 1-elementową tablicą pustego łańcucha, a nie 2 elementami. Sprawdziłem nawet starszą wersję bashu i nadal tam jest źle; jak mówisz set -xpokazuje, jak się rozwija. Chyba nie przetestowałem tego komentarza przed opublikowaniem. >. <Możesz sprawić, aby działał, ustawiając IFS=''(zapisz / przywróć go wokół tej instrukcji), ponieważ "${array[*]}"interpretacja oddziela elementy pierwszym znakiem IFS. (Lub spacja, jeśli nie jest ustawiona). Ale „ Jeśli IFS ma wartość NULL, parametry są łączone bez ingerencji w separatory. ” (Dokumenty dla parametrów pozycyjnych $ *, ale zakładam to samo dla tablic).
Peter Cordes
@Michael: Dodano odpowiedź, która to robi.
Peter Cordes
3

Sprawdziłem to za pomocą 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

i 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

W tym drugim przypadku potrzebujesz następującej konstrukcji:

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

aby nie zawiodła na pustej lub nieuzbrojonej tablicy. Tak, jeśli robisz tak set -eujak zwykle. Zapewnia to bardziej rygorystyczne sprawdzanie błędów. Z dokumentów :

-mi

Wyjdź natychmiast, jeśli potok (patrz Rurociągi), który może składać się z pojedynczego prostego polecenia (patrz Proste polecenia), listy (patrz Listy) lub polecenia złożonego (patrz Polecenia złożone) zwraca stan niezerowy. Powłoka nie kończy działania, jeśli polecenie, które się nie powiedzie, jest częścią listy poleceń bezpośrednio po słowie kluczowym chwilę lub do, część testu w instrukcji if, część dowolnego polecenia wykonanego w && lub || lista oprócz polecenia następującego po ostatnim && lub ||, dowolne polecenie w potoku, ale ostatnie, lub jeśli status zwracany polecenia jest odwracany za pomocą! Jeśli polecenie złożone inne niż podpowłoka zwraca stan niezerowy, ponieważ polecenie nie powiodło się podczas ignorowania -e, powłoka nie kończy działania. Pułapka na ERR, jeśli jest ustawiona, jest wykonywana przed wyjściem powłoki.

Ta opcja dotyczy środowiska powłoki i każdego środowiska podpowłoki osobno (patrz Środowisko wykonywania poleceń) i może spowodować zamknięcie podpowłoki przed wykonaniem wszystkich poleceń w podpowłoce.

Jeśli polecenie złożone lub funkcja powłoki jest wykonywana w kontekście, w którym ignorowane jest -e, żadne z poleceń wykonanych w ramach polecenia złożonego lub treści funkcji nie będzie miało wpływu na ustawienie -e, nawet jeśli -e jest ustawione, a polecenie zwraca status awarii. Jeśli polecenie złożone lub funkcja powłoki ustawi -e podczas wykonywania w kontekście, w którym -e jest ignorowane, ustawienie to nie będzie miało żadnego efektu, dopóki polecenie złożone lub polecenie zawierające wywołanie funkcji nie zostanie zakończone.

-u

Nieprzetworzone zmienne i parametry inne niż parametry specjalne „@” lub „*” traktują jak błąd podczas rozszerzania parametrów. Komunikat o błędzie zostanie zapisany do standardowego błędu, a nieinteraktywna powłoka zostanie zamknięta.

Jeśli nie potrzebujesz tego, możesz pominąć :+${array[@]}część.

Zauważ też, że konieczne jest [[tutaj korzystanie z operatora, ponieważ [otrzymasz:

$ 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
źródło
Z -utym powinieneś faktycznie skorzystać z ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski,
@JakubBochenski O jakiej wersji bash mówisz? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri
Problem w przykładzie z pojedynczymi nawiasami jest z @pewnością. Możesz użyć *rozszerzenia tablicy [ "${array[*]}" ], prawda? Nadal [[działa również dobrze. Zachowanie obu z nich dla tablicy z wieloma pustymi łańcuchami jest nieco zaskakujące. Oba [ ${#array[*]} ]i [[ "${array[@]}" ]]są fałszywe dla array=()i array=('')ale prawdziwe dla array=('' '')(dwóch lub więcej pustych ciągów). Jeśli chcesz, aby jeden lub więcej pustych ciągów zawierało wartość true, możesz użyć [ ${#array[@]} -gt 0 ]. Jeśli chcesz, aby wszystkie były fałszywe, być może możesz //je usunąć.
eisd
@eisd mógłbym użyć [ "${array[*]}" ], ale gdybym wpadł na takie wyrażenie, trudniej byłoby mi zrozumieć, co robi. Ponieważ [...]działa w kategoriach ciągów na wyniku interpolacji. W przeciwieństwie do [[...]], które mogą być świadome tego, co zostało interpolowane. Oznacza to, że może wiedzieć, że przekazano tablicę. [[ ${array[@]} ]]czyta mi jako „sprawdź, czy tablica nie jest pusta”, a [ "${array[*]}" ]jako „sprawdź, czy wynikiem interpolacji wszystkich elementów tablicy jest niepusty ciąg”.
x-yuri,
... Jeśli chodzi o zachowanie dwóch pustych łańcuchów, wcale mnie to nie dziwi. Zaskakujące jest zachowanie z jednym pustym ciągiem. Ale prawdopodobnie uzasadnione. Jeśli chodzi o [ ${#array[*]} ], prawdopodobnie miałeś na myśli [ "${array[*]}" ], ponieważ to pierwsze jest prawdziwe dla dowolnej liczby elementów. Ponieważ liczba elementów jest zawsze niepustym łańcuchem. Jeśli chodzi o ten drugi element z dwoma elementami, wyrażenie w nawiasach interpretacyjnych jest interpretowane jako ' 'niepusty ciąg. Co do tego [[ ${array[@]} ]], po prostu myślą (i słusznie), że jakakolwiek tablica dwóch elementów nie jest pusta.
x-yuri
2

Jeśli chcesz wykryć tablicę z pustymi elementami , jakarr=("" "") pusta, taka sama jakarr=()

Możesz wkleić wszystkie elementy razem i sprawdzić, czy wynik ma zerową długość. (Budowanie spłaszczonej kopii zawartości tablicy nie jest idealne pod względem wydajności, jeśli tablica może być bardzo duża. Miejmy nadzieję, że nie używasz bash dla takich programów ...)

Ale "${arr[*]}"rozwija się z elementami oddzielonymi przez pierwszą postać IFS. Musisz więc zapisać / przywrócić IFS i zrobić, IFS=''aby to działało, albo sprawdź, czy długość łańcucha == # elementów tablicy - 1. (Tablica nelementów ma n-1separatory). Aby poradzić sobie z tym osobno, najłatwiej jest uzupełnić konkatenację o 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

walizka testowa z 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

Niestety to nie powiedzie arr=(): [[ 1 -ne 0 ]]. Dlatego najpierw trzeba osobno sprawdzić, czy rzeczywiście są puste tablice.


Lub zIFS='' . Prawdopodobnie chcesz zapisać / przywrócić IFS zamiast używać podpowłoki, ponieważ nie możesz łatwo uzyskać wyniku z podpowłoki.

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

przykład:

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

ma pracować arr=()- to wciąż tylko pusty ciąg.

Peter Cordes
źródło
Głosowałem, ale zacząłem używać [[ "${arr[*]}" = *[![:space:]]* ]], ponieważ mogę liczyć na co najmniej jedną postać spoza WS.
Michael
@Michael: tak, to dobra opcja, jeśli nie musisz odrzucać arr=(" ").
Peter Cordes
0

W moim przypadku druga odpowiedź nie była wystarczająca, ponieważ mogły istnieć białe spacje. Przyszłam z:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi
Micha
źródło
echo | wcwydaje się niepotrzebnie nieefektywne w porównaniu z wbudowanymi powłokami.
Peter Cordes,
Nie jestem pewien, czy rozumiem @PeterCordes, czy mogę zmodyfikować drugie odpowiedzi [ ${#errors[@]} -eq 0 ];w celu obejścia problemu z białymi znakami ? Wolałbym również wbudowany.
Micha,
W jaki sposób spacja powoduje problem? $#rozwija się do liczby i działa dobrze nawet po opts+=(""). np. unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"i dostaję 2. Czy możesz pokazać przykład czegoś, co nie działa?
Peter Cordes,
Dawno, dawno temu. IIRC źródło pochodzenia zawsze drukuje co najmniej „”. Dlatego dla opts = "" lub opts = ("") potrzebowałem 0, a nie 1, ignorując pusty znak nowej linii lub pusty ciąg.
Michał
Ok, więc musisz traktować opts=("")tak samo jak opts=()? To nie jest pusta tablica, ale możesz sprawdzić pustą tablicę lub pusty pierwszy element za pomocą opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Zauważ, że twoja obecna odpowiedź mówi „brak opcji” dla opts=("" "-foo"), co jest całkowicie fałszywe, a to odtwarza to zachowanie. Można [[ -z "${opts[*]}" ]]Chyba, interpolować wszystkie elementy tablicy do płaskiej ciąg, który -zsprawdza czy długość niezerową. Jeśli sprawdzenie pierwszego elementu jest wystarczające, -z "$opts"działa.
Peter Cordes,