Jak uzyskać wartość wyjściową i wyjściową podpowłoki, używając „bash -e”?

70

Rozważ następujący kod

external-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

inner-scope.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

Próbuję outer-scope.shwyjść, gdy połączenie się inner()nie powiedzie. Ponieważ $()wywołuje podpowłokę, tak się nie dzieje.

Jak inaczej uzyskać wynik działania funkcji, zachowując fakt, że funkcja może wyjść z niezerowym kodem wyjścia?

Jabalsad
źródło

Odpowiedzi:

102

$()zachowuje status wyjścia; musisz po prostu użyć go w oświadczeniu, które nie ma własnego statusu, na przykład przydziału.

wynik = $ (wewnętrzny)

Następnie $?będzie zawierać status wyjścia inneri możesz użyć do tego różnego rodzaju sprawdzeń:

output=$(inner) || exit $?
echo $output

Lub:

if ! output=$(inner); then
    exit $?
fi
echo $output

Lub:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(Uwaga: goły exitbez argumentów jest równoważny exit $?- oznacza to, że kończy się ze statusem wyjścia ostatniego polecenia. Użyłem drugiej formy tylko dla przejrzystości).


Dla przypomnienia: sourcew tym przypadku jest całkowicie niezwiązany. Możesz po prostu zdefiniować inner()w outer-scope.shpliku z tymi samymi wynikami.

grawitacja
źródło
Dlaczego tak jest, mimo że $? zawiera status wyjścia $ (), że skrypt nie wychodzi automatycznie (biorąc pod uwagę, że -e jest ustawiony)? EDYCJA: nieważne, myślę, że odpowiedziałeś na moje pytania, dzięki!
jabalsad
Nie jestem pewny. (Nie testowałem żadnego z powyższych.) Ale istnieją pewne ograniczenia dotyczące -e, wszystkie wyjaśnione na stronie podręcznika bash; jeśli pytasz o echo $()to, może to być spowodowane tym, że kody wyjścia podpowłoki są ignorowane, gdy wiersz - echopolecenie - ma własny kod wyjścia (zwykle 0).
grawity
1
Hmm, kiedy piszę if ! $(exit 1) ; then echo $?; fi, rozumiem 0. Nie jestem pewien, ifjak to zrobić, jeśli chcesz zachować tę wartość wyjściową.
Ron Burk
@grawity - Czy to działa z Dash? Próbuję obejść pewne irytujące zachowanie Autoconf (a mianowicie raportowanie sukcesu Autoconf, gdy Sun lub kompilator IBM drukuje nielegalną opcję na terminalu).
jww
3
if ! output=$(inner); then exit $?; fizakończy działanie z kodem powrotu 0, ponieważ $?poda kod powrotu !zamiast kodu powrotu z inner. Możesz uzyskać pożądane zachowanie, if output=$(inner); then : ; else exit $?; fiale to oczywiście bardziej szczegółowe
SJL
26

Zobacz BashFAQ / 002 :

Jeśli chcesz zarówno status (wyjściowy, jak i wyjściowy):

output=$(command)
status=$? 

Specjalny przypadek

Uwaga na temat trudnego przypadku ze zmiennymi lokalnymi funkcji, porównaj następujący kod:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

Dostaniemy:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # a good one
output:data, status:1

Dlaczego?

Gdy dane wyjściowe podpowłoki są używane do inicjalizacji localzmiennej, stanem wyjścia nie jest już podpowłoka, ale polecenie , które najprawdopodobniej będzie .local 0

Zobacz także https://stackoverflow.com/a/4421282/537554

ryenus
źródło
3
Chociaż tak naprawdę nie odpowiedziałem na pytanie, przydało mi się dzisiaj, więc +1.
fourpastmidnight
1
Status wyjścia dla poleceń bash jest zawsze statusem ostatniego wykonanego polecenia. Kiedy spędzamy tyle czasu w silnie pisanych językach, łatwo zapomnieć, że „lokalny” nie jest specyfikatorem typu, ale tylko innym poleceniem. Dziękuję za powtórzenie tego punktu tutaj, pomogło mi dzisiaj.
markeissler
2
Wow, właśnie natrafiłem na ten konkretny problem, a ty go wyjaśniłeś. Dzięki!
krb686
4
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

Dodanie echopodpowłoki nie jest samodzielne (nie jest osobno sprawdzane) i nie jest przerywane. Przypisanie omija ten problem.

Możesz to również zrobić i przekierować dane wyjściowe do pliku, aby później je przetworzyć.

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile
Daniel Beck
źródło
Oczywiście plik $ tmp nadal istnieje w drugim wariancie ...
Daniel Beck