Mam skrypt, który nie wychodzi, kiedy chcę.
Przykładowy skrypt z tym samym błędem to:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
Zakładam, że zobaczę wynik:
:~$ ./test.sh
1
:~$
Ale tak naprawdę widzę:
:~$ ./test.sh
1
2
:~$
Czy ()
tworzenie łańcuchów poleceń w jakiś sposób tworzy zakres? Z czego exit
wychodzi, jeśli nie skrypt?
shell-script
exit
subshell
Minix
źródło
źródło
Odpowiedzi:
()
uruchamia polecenia w podpowłoce, więc poexit
wyjściu z podpowłoki wracasz do powłoki nadrzędnej. Użyj nawiasów klamrowych,{}
jeśli chcesz uruchamiać polecenia w bieżącej powłoce.Z podręcznika bash:
Warto wspomnieć, że składnia powłoki jest dość spójna, a podpowłoka uczestniczy również w innych
()
konstrukcjach, takich jak podstawienie poleceń (również ze`..`
składnią w starym stylu ) lub podstawienie procesu, więc następujące elementy nie wyjdą z bieżącej powłoki:Chociaż może być oczywiste, że podpowłoki są zaangażowane, gdy polecenia są umieszczane jawnie w środku
()
, mniej widocznym faktem jest to, że są one również spawnowane w tych innych strukturach:polecenie uruchomiono w tle
nie wychodzi z bieżącej powłoki, ponieważ (po
man bash
)rurociąg
nadal wychodzi tylko z podpowłoki.
Jednak różne powłoki zachowują się inaczej pod tym względem. Na przykład
bash
umieszcza wszystkie komponenty potoku w osobnych podpowłokach (chyba że użyjesz tejlastpipe
opcji w wywołaniach, w których kontrola zadań nie jest włączona), ale AT&Tksh
izsh
uruchomisz ostatnią część w bieżącej powłoce (oba zachowania są dozwolone przez POSIX). A zatemw zasadzie nic nie robi w bashu, ale wychodzi z zsh z powodu ostatniego
exit
.coproc exit
działa równieżexit
w podpowłoce.źródło
{
i}
nie są składni, są zarezerwowane słowa i musi być otoczony przestrzeni, a lista musi kończyć się średnikiem (terminator poleceń, nowego wiersza, ampersand)(echo $$)
drukuje identyfikator powłoki nadrzędnej, ponieważ$$
jest rozszerzany nawet przed utworzeniem podpowłoki. W rzeczywistości drukowanie identyfikatora procesu podpowłoki może być trudne, patrz stackoverflow.com/questions/9119885/…$$
jest rozwijane przed utworzeniem podpowłoki, a mimo to$BASHPID
pokazuje prawidłową wartość podpowłoki?Wykonanie
exit
w podpowłoce to jedna pułapka:Skrypt wypisuje 42, wychodzi z podpowłoki z kodem powrotu
1
i kontynuuje wykonywanie skryptu. Nawet zastąpienie wywołaniaecho $(CALC) || exit 1
nie pomaga, ponieważ kod powrotuecho
wynosi 0 niezależnie od kodu powrotucalc
. Icalc
jest wykonywany przedecho
.Jeszcze więcej zagadek niweczy efekt
exit
zawijania go dolocal
wbudowanego, jak w poniższym skrypcie. Natknąłem się na problem, gdy napisałem funkcję do weryfikacji wartości wejściowej. Przykład:Chcę utworzyć plik o nazwie „rok miesiąc dzień.log”, tj.
20141211.log
Na dziś. Data jest wprowadzana przez użytkownika, który może nie podać rozsądnej wartości. Dlatego w mojej funkcjifname
sprawdzam wartość zwracaną wdate
celu sprawdzenia poprawności danych wejściowych użytkownika:Wygląda dobrze. Niech skrypt ma nazwę
s.sh
. Jeśli użytkownik wywoła skrypt za pomocą./s.sh "Thu Dec 11 20:45:49 CET 2014"
, plik20141211.log
zostanie utworzony. Jeśli jednak użytkownik wpisze./s.sh "Thu hec 11 20:45:49 CET 2014"
, skrypt wyświetli:Linia
fname…
mówi, że w podpowłoce wykryto złe dane wejściowe. Aleexit 1
konieclocal …
linii nigdy się nie uruchamia, ponieważlocal
dyrektywa zawsze powraca0
. Wynika to z faktu, żelocal
jest wykonywany po,$(fname)
a tym samym zastępuje swój kod powrotu. Z tego powodu skrypt jest kontynuowany i wywołuje siętouch
z pustym parametrem. Ten przykład jest prosty, ale zachowanie bash może być mylące w prawdziwej aplikacji. Wiem, prawdziwi programiści nie używają miejscowychAby było to jasne: bez
local
skryptu zostanie przerwany zgodnie z oczekiwaniami po wprowadzeniu niepoprawnej daty.Rozwiązaniem jest podzielenie linii jak
Dziwne zachowanie jest zgodne z dokumentacją strony
local
podręcznika bash: „Zwracany status to 0, chyba że lokalny jest używany poza funkcją, podana jest niepoprawna nazwa lub nazwa jest zmienną tylko do odczytu”.Chociaż nie jestem błędem, uważam, że zachowanie bash jest sprzeczne z intuicją. Zdaję sobie sprawę z sekwencji wykonania,
local
nie powinien jednak maskować zepsutego zadania.Moja wstępna odpowiedź zawierała pewne niedokładności. Po odkrywczej i dogłębnej dyskusji z mikeserv (dziękuję za to) postanowiłem je naprawić.
źródło
doit()
.Rzeczywiste rozwiązanie:
Grupowanie błędów zostanie wykonane tylko wtedy, gdy
bla
zwróci status błędu, iexit
nie znajduje się w podpowłoce, więc cały skrypt zatrzymuje się.źródło
Nawiasy otwierają podpowłokę, a wyjście wychodzi tylko z tej podpowłoki.
Możesz odczytać kod wyjścia za pomocą
$?
i dodać go do skryptu, aby wyjść ze skryptu, jeśli opuścisz podpowłokę:źródło