Piszę skrypt basha, który ma set -u
i mam problem z rozszerzeniem pustej tablicy: bash wydaje się traktować pustą tablicę jako zmienną nieustawioną podczas rozwijania:
$ set -u
$ arr=()
$ echo "foo: '${arr[@]}'"
bash: arr[@]: unbound variable
( declare -a arr
też nie pomaga.)
Typowym rozwiązaniem jest użycie ${arr[@]-}
zamiast tego zastępowania pustego ciągu zamiast („niezdefiniowanej”) pustej tablicy. Nie jest to jednak dobre rozwiązanie, ponieważ teraz nie można odróżnić tablicy zawierającej pojedynczy pusty ciąg znaków od pustej tablicy. (@ -expansion jest specjalne w bash, rozszerza się "${arr[@]}"
do "${arr[0]}" "${arr[1]}" …
, co czyni go idealnym narzędziem do budowania linii poleceń.)
$ countArgs() { echo $#; }
$ countArgs a b c
3
$ countArgs
0
$ countArgs ""
1
$ brr=("")
$ countArgs "${brr[@]}"
1
$ countArgs "${arr[@]-}"
1
$ countArgs "${arr[@]}"
bash: arr[@]: unbound variable
$ set +u
$ countArgs "${arr[@]}"
0
Czy jest więc sposób obejścia tego problemu, inny niż sprawdzenie długości tablicy w if
(zobacz przykład kodu poniżej) lub wyłączenie -u
ustawienia dla tego krótkiego fragmentu?
if [ "${#arr[@]}" = 0 ]; then
veryLongCommandLine
else
veryLongCommandLine "${arr[@]}"
fi
Aktualizacja: Usunięto bugs
tag z powodu wyjaśnienia przez ikegami.
"${arr[@]}"
. Czy coś mi brakuje? Z tego, co widzę, działa przynajmniej w5.x
.Zgodnie z dokumentacją
Żaden indeks nie ma przypisanej wartości, więc tablica nie jest ustawiona.
Ale chociaż dokumentacja sugeruje, że błąd jest tutaj odpowiedni, nie ma to już miejsca od 4.4 .
$ bash --version | head -n 1 GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu) $ set -u $ arr=() $ echo "foo: '${arr[@]}'" foo: ''
Istnieje warunek, którego możesz użyć w tekście, aby osiągnąć to, czego chcesz w starszych wersjach: Użyj
${arr[@]+"${arr[@]}"}
zamiast"${arr[@]}"
.$ function args { perl -E'say 0+@ARGV; say "$_: $ARGV[$_]" for 0..$#ARGV' -- "$@" ; } $ set -u $ arr=() $ args "${arr[@]}" -bash: arr[@]: unbound variable $ args ${arr[@]+"${arr[@]}"} 0 $ arr=("") $ args ${arr[@]+"${arr[@]}"} 1 0: $ arr=(a b c) $ args ${arr[@]+"${arr[@]}"} 3 0: a 1: b 2: c
Testowane w bash 4.2.25 i 4.3.11.
źródło
[@]+
tak naprawdę robi i dlaczego druga${arr[@]}
nie spowoduje niezwiązanego błędu.${parameter+word}
rozwija się tylkoword
wtedy, gdyparameter
nie jest nieustawiona.${arr+"${arr[@]}"}
jest krótszy i wydaje się działać równie dobrze.unset arr
,arr[1]=a
,args ${arr+"${arr[@]}"}
Vsargs ${arr[@]+"${arr[@]}"}
+
rozwinięcie nie występuje (a mianowicie pusta tablica), interpretacja jest zastępowana niczym , co jest dokładnie tym, do czego rozwija się pusta tablica.:+
jest niebezpieczny, ponieważ traktuje również('')
tablicę jednoelementową jako nieustawioną i podobnie rozwija się do niczego, tracąc wartość.Zaakceptowana odpowiedź @ ikegami jest subtelnie błędna! Prawidłowa inkantacja to
${arr[@]+"${arr[@]}"}
:$ countArgs () { echo "$#"; } $ arr=('') $ countArgs "${arr[@]:+${arr[@]}}" 0 # WRONG $ countArgs ${arr[@]+"${arr[@]}"} 1 # RIGHT $ arr=() $ countArgs ${arr[@]+"${arr[@]}"} 0 # Let's make sure it still works for the other case...
źródło
bash-4.4.23
:arr=('') && countArgs "${arr[@]:+${arr[@]}}"
produkuje1
. Ale${arr[@]+"${arr[@]}"}
forma pozwala na rozróżnienie między pustymi / niepustymi wartościami poprzez dodanie / nie dodanie dwukropka.arr=('') && countArgs ${arr[@]:+"${arr[@]}"}
->0
,arr=('') && countArgs ${arr[@]+"${arr[@]}"}
->1
.Okazuje się, że obsługa tablic została zmieniona w niedawno wydanym (2016/09/16) bash 4.4 (dostępnym na przykład w Debianie stretch).
Teraz rozszerzenie pustych tablic nie generuje ostrzeżenia
$ set -u $ arr=() $ echo "${arr[@]}" $ # everything is fine
źródło
bash-4.4.12
"${arr[@]}"
wystarczy.może to być kolejna opcja dla tych, którzy wolą nie powielać arr [@] i mogą mieć pusty łańcuch
echo "foo: '${arr[@]:-}'"
testować:
set -u arr=() echo a "${arr[@]:-}" b # note two spaces between a and b for f in a "${arr[@]:-}" b; do echo $f; done # note blank line between a and b arr=(1 2) echo a "${arr[@]:-}" b for f in a "${arr[@]:-}" b; do echo $f; done
źródło
for
, skończy się to pojedynczym pustym ciągiem, gdy tablica jest niezdefiniowana / zdefiniowana jako pusta, gdzie możesz chcieć treści pętli nie działać, jeśli tablica nie jest zdefiniowana.${arr[@]+"${arr[@]}"}
, poprawnie zachowuje stan pustej tablicy.Odpowiedź @ ikegami jest poprawna, ale uważam składnię za okropną
${arr[@]+"${arr[@]}"}
. Jeśli używasz długich nazw zmiennych tablicowych, zaczyna wyglądać jak spaghetti szybciej niż zwykle.Spróbuj tego zamiast tego:
Wygląda na to, że operator wycinka tablicy Bash jest bardzo wyrozumiały.
Dlaczego więc Bash tak utrudnił obsługę skrajnego przypadku tablic? Westchnienie. Nie mogę zagwarantować, że wersja pozwoli na takie nadużycie operatora wycinka tablicy, ale dla mnie działa elegancko.
Uwaga: używam
GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
Twój przebieg może się różnić.źródło
"${arr[@]:0}"
daje-bash: arr[@]: unbound variable
.arr=("_dummy_")
i używanie rozszerzenia${arr[@]:1}
wszędzie. Wspomina się o tym w innych odpowiedziach, odnosząc się do wartości wartowniczych.Rzeczywiście „ciekawa” niespójność.
Ponadto,
$ set -u $ echo $# 0 $ echo "$1" bash: $1: unbound variable # makes sense (I didn't set any) $ echo "$@" | cat -e $ # blank line, no error
Chociaż zgadzam się, że obecne zachowanie może nie być błędem w tym sensie, który wyjaśnia @ikegami, IMO możemy powiedzieć, że błąd znajduje się w samej definicji („zestawu”) i / lub w fakcie, że jest stosowany niespójnie. Poprzedni akapit na stronie podręcznika mówi
co jest całkowicie zgodne z tym, co mówi o rozszerzaniu parametrów pozycyjnych w
"$@"
. Nie, że nie ma innych niespójności w zachowaniu tablic i parametrów pozycyjnych ... ale dla mnie nie ma żadnej wskazówki, że ten szczegół powinien być niespójny między nimi.Kontynuacja,
$ arr=() $ echo "${arr[@]}" bash: arr[@]: unbound variable # as we've observed. BUT... $ echo "${#arr[@]}" 0 # no error $ echo "${!arr[@]}" | cat -e $ # no error
Więc
arr[]
czy nie jest tak niezwiązany, że nie możemy uzyskać liczby jego elementów (0) lub (pustej) listy jego kluczy? Są dla mnie sensowne i użyteczne - jedyną wartością odstającą wydaje się być${arr[@]}
(i${arr[*]}
) ekspansja.źródło
Uzupełniam odpowiedzi na pytania @ ikegami (zaakceptowane) i @ kevinarpe (również dobre).
Możesz zrobić,
"${arr[@]:+${arr[@]}}"
aby obejść problem. Prawa strona (tj. Po:+
) zawiera wyrażenie, które będzie używane w przypadku, gdy lewa strona nie jest zdefiniowana / zerowa.Składnia jest tajemnicza. Zwróć uwagę, że prawa strona wyrażenia będzie podlegać interpretacji parametrów, dlatego należy zwrócić szczególną uwagę na spójne cytowanie.
: example copy arr into arr_copy arr=( "1 2" "3" ) arr_copy=( "${arr[@]:+${arr[@]}}" ) # good. same quoting. # preserves spaces arr_copy=( ${arr[@]:+"${arr[@]}"} ) # bad. quoting only on RHS. # copy will have ["1","2","3"], # instead of ["1 2", "3"]
Jak wspomina @kevinarpe, mniej tajemną składnią jest użycie notacji wycinka tablicy
${arr[@]:0}
(w wersjach Bash>= 4.4
), która rozszerza się na wszystkie parametry, zaczynając od indeksu 0. Nie wymaga również tak dużej liczby powtórzeń. To rozszerzenie działa niezależnie od tegoset -u
, więc możesz z niego korzystać przez cały czas. Strona podręcznika mówi (pod rozszerzeniem parametrów ):Oto przykład dostarczony przez @kevinarpe, z alternatywnym formatowaniem, aby umieścić dane wyjściowe jako dowód:
set -u function count() { echo $# ; }; ( count x y z ) : prints "3" ( arr=() count "${arr[@]}" ) : prints "-bash: arr[@]: unbound variable" ( arr=() count "${arr[@]:0}" ) : prints "0" ( arr=(x y z) count "${arr[@]:0}" ) : prints "3"
To zachowanie różni się w zależności od wersji Bash. Być może zauważyłeś również, że operator długości
${#arr[@]}
zawsze będzie obliczał0
dla pustych tablic, niezależnie od tegoset -u
, bez powodowania „błędu niezwiązanej zmiennej”.źródło
:0
idiom zawodzi w Bash 4.2, więc nie jest to bezpieczne podejście. Zobacz moją odpowiedź .Oto kilka sposobów na zrobienie czegoś takiego, jeden za pomocą wartowników, a drugi za pomocą dołączeń warunkowych:
#!/bin/bash set -o nounset -o errexit -o pipefail countArgs () { echo "$#"; } arrA=( sentinel ) arrB=( sentinel "{1..5}" "./*" "with spaces" ) arrC=( sentinel '$PWD' ) cmnd=( countArgs "${arrA[@]:1}" "${arrB[@]:1}" "${arrC[@]:1}" ) echo "${cmnd[@]}" "${cmnd[@]}" arrA=( ) arrB=( "{1..5}" "./*" "with spaces" ) arrC=( '$PWD' ) cmnd=( countArgs ) # Checks expansion of indices. [[ ! ${!arrA[@]} ]] || cmnd+=( "${arrA[@]}" ) [[ ! ${!arrB[@]} ]] || cmnd+=( "${arrB[@]}" ) [[ ! ${!arrC[@]} ]] || cmnd+=( "${arrC[@]}" ) echo "${cmnd[@]}" "${cmnd[@]}"
źródło
Ciekawa niespójność; pozwala to zdefiniować coś, co nie jest uważane za ustawione, ale pojawia się na wyjściu programu
declare -p
arr=() set -o nounset echo ${arr[@]} => -bash: arr[@]: unbound variable declare -p arr => declare -a arr='()'
AKTUALIZACJA: jak wspominali inni, naprawiono w 4.4 wydanym po opublikowaniu tej odpowiedzi.
źródło
echo ${arr[@]}
(ale przed Bash 4.4 nadal będziesz widzieć błąd).echo $arr[@]
sam, zobaczyłbyś, że komunikat o błędzie jest inny.Wydaje się, że najprostszym i najbardziej kompatybilnym sposobem jest:
$ set -u $ arr=() $ echo "foo: '${arr[@]-}'"
źródło