Mam dwa procesy fooi barpołączone z rurą:
$ foo | bar
barzawsze kończy 0; Interesuje mnie kod wyjścia foo. Czy jest na to jakiś sposób?
Mam dwa procesy fooi barpołączone z rurą:
$ foo | bar
barzawsze kończy 0; Interesuje mnie kod wyjścia foo. Czy jest na to jakiś sposób?
Odpowiedzi:
Jeśli używasz bash, możesz użyć PIPESTATUSzmiennej tablicowej, aby uzyskać status wyjścia każdego elementu potoku.
$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0
Jeśli używasz zsh, tablica jest nazywana pipestatus(wielkość liter ma znaczenie!), A indeksy tablic zaczynają się od jednego:
$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0
Aby połączyć je w ramach funkcji w sposób, który nie powoduje utraty wartości:
$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0
Uruchom powyższe w bashlub, zsha otrzymasz takie same wyniki; tylko jeden retval_bashi retval_zshzostanie ustawiona. Drugi będzie pusty. Pozwoliłoby to na zakończenie funkcji return $retval_bash $retval_zsh(zwróć uwagę na brak cudzysłowów!).
pipestatusw Zsh. Niestety inne pociski nie mają tej funkcji.
echo "$pipestatus[1]" "$pipestatus[2]".
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Można to zrobić na 3 sposoby:
Pierwszym sposobem jest ustawienie pipefailopcji ( ksh, zshlub bash). Jest to najprostsze i zasadniczo ustawia status $?wyjścia na kod wyjścia ostatniego programu, aby wyjść z niezerowego (lub zerowego, jeśli wszystkie zakończyły się pomyślnie).
$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1
Bash ma również zmienną tablicową o nazwie $PIPESTATUS( $pipestatusin zsh), która zawiera status wyjścia wszystkich programów w ostatnim potoku.
$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1
Możesz użyć trzeciego przykładu polecenia, aby uzyskać określoną wartość w potoku, której potrzebujesz.
To najbardziej niewygodne z rozwiązań. Uruchom każdą komendę osobno i zapisz status
$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1
ksh, ale po krótkim spojrzeniu na stronę podręczną nie obsługuje $PIPESTATUSani nic podobnego. pipefailJednak obsługuje tę opcję.
LOG=$(failed_command | successful_command)
To rozwiązanie działa bez użycia specyficznych funkcji bash lub plików tymczasowych. Premia: w końcu status wyjścia jest tak naprawdę statusem wyjścia, a nie ciągiem znaków w pliku.
Sytuacja:
someprog | filter
chcesz status wyjścia z someprogi wyjście z filter.
Oto moje rozwiązanie:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
wynikiem tego konstruktu jest stdout od filterjako stdout konstrukcji i status wyjścia od someprogjako status wyjścia konstrukcji.
konstrukcja ta działa również z prostym grupowaniem poleceń {...}zamiast podpowłok (...). podpowłoki mają pewne implikacje, między innymi koszt wydajności, którego tutaj nie potrzebujemy. przeczytaj instrukcję fine bash, aby uzyskać więcej informacji: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1
Niestety gramatyka bash wymaga spacji i średników dla nawiasów klamrowych, dzięki czemu konstrukcja staje się znacznie bardziej przestronna.
Do końca tego tekstu użyję wariantu podpowłoki.
Przykład someprogi filter:
someprog() {
echo "line1"
echo "line2"
echo "line3"
return 42
}
filter() {
while read line; do
echo "filtered $line"
done
}
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
Przykładowe dane wyjściowe:
filtered line1
filtered line2
filtered line3
42
Uwaga: proces potomny dziedziczy otwarte deskryptory plików od rodzica. Oznacza to, someprogże odziedziczy otwarty deskryptor pliku 3 i 4. Jeśli someprogzapisze w deskryptorze pliku 3, stanie się to kodem wyjścia. Rzeczywisty status wyjścia zostanie zignorowany, ponieważ readczyta tylko raz.
Jeśli obawiasz się, że możesz someprognapisać do deskryptora pliku 3 lub 4, najlepiej zamknąć deskryptory plików przed wywołaniem someprog.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
Znak exec 3>&- 4>&-przed someprogzamyka deskryptor pliku przed uruchomieniem, someprogwięc dla someprogtych deskryptorów plików po prostu nie istnieją.
Można go również napisać w ten sposób: someprog 3>&- 4>&-
Objaśnienie konstrukcji krok po kroku:
( ( ( ( someprog; #part6
echo $? >&3 #part5
) | filter >&4 #part4
) 3>&1 #part3
) | (read xs; exit $xs) #part2
) 4>&1 #part1
Od dołu do góry:
#part3) i prawej ( #part2). exit $xsjest także ostatnim poleceniem potoku, co oznacza, że ciąg ze standardowego wejścia będzie statusem wyjścia całej konstrukcji.#part2a z kolei stanie się kodem wyjścia całej konstrukcji.#part5i #part6) i po prawej ( filter >&4). Dane wyjściowe filtersą przekierowywane do deskryptora pliku 4. W #part1deskryptorze pliku 4 został przekierowany na standardowe wyjście. Oznacza to, że wynikiem filterjest stdout całego konstruktu.#part6jest drukowany do deskryptora pliku 3. W #part3deskryptorze pliku 3 został przekierowany do #part2. Oznacza to, że status wyjścia z #part6będzie końcowym statusem wyjścia dla całej konstrukcji.someprogjest wykonywany. Status wyjścia jest brany pod uwagę #part5. Stdout jest pobierany przez rurkę #part4i przekazywany do filter. Wyjście z filterwill osiąga z kolei standardowe wyjście, jak wyjaśniono w#part4(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)upraszcza someprog 3>&- 4>&-.
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Chociaż nie do końca to, o co prosiłeś, możesz użyć
#!/bin/bash -o pipefail
dzięki czemu Twoje rury zwracają ostatni niezerowy zwrot.
może być nieco mniej kodowania
Edycja: przykład
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1
set -o pipefailwewnątrz skryptu powinna być bardziej niezawodna, np. na wypadek, gdyby ktoś wykonał skrypt za pośrednictwem bash foo.sh.
-o pipefailnie ma go w POSIX.
#!/bin/bash -o pipefail. Błąd to:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!liniach poza pierwszym, a więc staje się to /bin/bash -o pipefail /tmp/ff, zamiast koniecznej /bin/bash -o pipefail /tmp/ff- getopt(lub podobnym) parsowania pomocą optarg, która jest następna pozycja w ARGV, jako argument do -o, więc to się nie powiedzie. Jeśli miałbyś zrobić opakowanie (powiedzmy, bash-pfże właśnie to zrobiło exec /bin/bash -o pipefail "$@", i umieść to w #!wierszu, to by działało. Zobacz także: en.wikipedia.org/wiki/Shebang_%28Unix%29
Co zrobić, jeśli to możliwe jest, aby karmić kod wyjścia z foopod bar. Na przykład, jeśli wiem, że foonigdy nie tworzy wiersza zawierającego tylko cyfry, mogę po prostu podać kod wyjścia:
{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'
Lub jeśli wiem, że dane wyjściowe foonigdy nie zawierają wiersza zawierającego tylko .:
{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'
Można to zawsze zrobić, jeśli istnieje jakiś sposób na rozpoczęcie barpracy na wszystkich wierszach oprócz ostatniego i przekazanie ostatniego wiersza jako kodu wyjścia.
W barprzypadku złożonego potoku, którego dane wyjściowe nie są potrzebne, można go obejść, drukując kod wyjścia na innym deskryptorze pliku.
exit_codes=$({ { foo; echo foo:"$?" >&3; } |
{ bar >/dev/null; echo bar:"$?" >&3; }
} 3>&1)
Po tym $exit_codeszwykle jest foo:X bar:Y, ale może się zdarzyć, bar:Y foo:Xjeśli barzakończy się przed odczytaniem wszystkich danych wejściowych lub jeśli nie będziesz miał szczęścia. Myślę, że zapisy do rur do 512 bajtów są atomowy na wszystkich Uniksach, więc foo:$?i bar:$?części nie zostaną wymieszane tak długo jak struny tag są pod 507 bajtów.
Jeśli musisz przechwycić dane wyjściowe bar, staje się to trudne. Możesz połączyć powyższe techniki, ustawiając, aby wyjście barnigdy nie zawierało wiersza, który wygląda jak kod wyjścia, ale robi się niezręcznie.
output=$(echo;
{ { foo; echo foo:"$?" >&3; } |
{ bar | sed 's/^/^/'; echo bar:"$?" >&3; }
} 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')
I oczywiście istnieje prosta opcja użycia pliku tymczasowego do przechowywania statusu. Proste, ale nie takie proste w produkcji:
/tmpjest to jedyne miejsce, w którym skrypt z pewnością będzie w stanie zapisywać pliki. Użyj mktemp, który nie jest POSIX, ale jest obecnie dostępny na wszystkich poważnych jednorożcach.foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
Zaczynając od rurociągu:
foo | bar | baz
Oto ogólne rozwiązanie wykorzystujące tylko powłokę POSIX i brak plików tymczasowych:
exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
(bar || echo "1:$?" >&3) |
(baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-
$error_statuses zawiera kody statusu wszystkich nieudanych procesów, w losowej kolejności, wraz z indeksami wskazującymi, które polecenie emitowało każdy status.
# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2
# test if all commands succeeded:
test -z "$error_statuses"
# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null
Zwróć uwagę na cytaty $error_statusesz moich testów; bez nich grepnie można odróżnić, ponieważ nowe linie są wymuszane na spacje.
Chciałem więc udzielić odpowiedzi podobnej do lesmany, ale myślę, że moja jest może nieco prostsza i nieco bardziej korzystna dla powłoki z czystej powłoki Bourne'a:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.
Myślę, że najlepiej to wyjaśnić od środka - polecenie1 wykona i wydrukuje swoje standardowe wyjście na stdout (deskryptor pliku 1), a następnie, gdy to zrobi, printf wykona i wydrukuje kod wyjścia polecenia 1 na swoim wyjściu, ale to wyjście jest przekierowane do deskryptor pliku 3.
Gdy polecenie1 jest uruchomione, jego standardowe wyjście jest przesyłane potokowo do polecenia2 (dane wyjściowe printf nigdy nie powodują, że jest to polecenie2, ponieważ wysyłamy go do deskryptora pliku 3 zamiast 1, co odczytuje potok). Następnie przekierowujemy dane wyjściowe polecenia 2 do deskryptora pliku 4, tak aby pozostało ono również poza deskryptorem pliku 1 - ponieważ chcemy, aby deskryptor pliku 1 był wolny przez trochę później, ponieważ sprowadzimy dane wyjściowe printf z deskryptora pliku 3 z powrotem do deskryptora pliku 1 - ponieważ to jest to, co przechwyci podstawienie polecenia (backticks) i to zostanie umieszczone w zmiennej.
Ostatnia magia polega na tym, że najpierw exec 4>&1zrobiliśmy to jako osobne polecenie - otwiera deskryptor pliku 4 jako kopię standardowego wejścia zewnętrznej powłoki. Podstawienie polecenia uchwyci wszystko, co jest napisane na zewnątrz z perspektywy poleceń w nim zawartych - ale ponieważ dane wyjściowe polecenia 2 przejdą do deskryptora pliku 4, jeśli chodzi o podstawienie polecenia, podstawienie polecenia nie przechwytuje go - jednak kiedy „wyjdzie” z podstawienia polecenia, nadal skutecznie przechodzi do ogólnego deskryptora pliku skryptu 1.
( exec 4>&1Musi to być osobne polecenie, ponieważ wielu powszechnym powłokom się to nie podoba, gdy próbujesz zapisać deskryptor pliku w podstawieniu polecenia, które jest otwierane w poleceniu „zewnętrznym”, które używa podstawienia. Więc to jest najprostszy przenośny sposób to zrobić.)
Możesz na to spojrzeć w mniej techniczny i bardziej zabawny sposób, tak jakby wyjścia poleceń przeskakiwały sobie nawzajem: polecenie1 potokuje do polecenia 2, następnie dane wyjściowe printf przeskakują nad poleceniem 2, aby polecenie 2 go nie złapało, a następnie dane wyjściowe polecenia 2 przeskakują w górę i w dół od podstawienia polecenia, tak jak printf ląduje w samą porę, aby zostać przechwyconym przez podstawienie, tak że kończy się w zmiennej, a wyjście polecenia 2 idzie wesoło zapisywane na standardowe wyjście, tak jak w normalnej rurze.
Ponadto, jak rozumiem, $?nadal będzie zawierał kod powrotu drugiego polecenia w potoku, ponieważ przypisania zmiennych, podstawienia poleceń i polecenia złożone są skutecznie przezroczyste dla kodu powrotu polecenia wewnątrz nich, więc status powrotu polecenie2 powinno zostać rozpowszechnione - to, i nie musi definiować dodatkowej funkcji, dlatego myślę, że może to być nieco lepsze rozwiązanie niż zaproponowane przez lesmana.
Zgodnie ze wspomnianymi zastrzeżeniami lesmana możliwe jest, że polecenie1 kiedyś skończy z deskryptorami plików 3 lub 4, więc aby być bardziej niezawodnym, wykonaj następujące czynności:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
Zauważ, że używam poleceń złożonych w moim przykładzie, ale podpowłoki (używanie ( )zamiast { }też również będzie działać, chociaż być może może być mniej wydajne).
Polecenia dziedziczą deskryptory plików z procesu, który je uruchamia, więc cała druga linia odziedziczy deskryptor pliku czwarty, a następnie złożone polecenie 3>&1odziedziczy deskryptor pliku trzy. Dlatego 4>&-upewnia się, że wewnętrzna komenda złożona nie odziedziczy deskryptora pliku cztery, i 3>&-nie odziedziczy deskryptora pliku trzy, więc polecenie1 otrzymuje „czystsze”, bardziej standardowe środowisko. Możesz także przesunąć wnętrze 4>&-obok 3>&-, ale myślę, że nie ograniczaj jego zakresu tak bardzo, jak to możliwe.
Nie jestem pewien, jak często rzeczy bezpośrednio używają deskryptora pliku trzeciego i czwartego - myślę, że większość programów używa syscall, które zwracają nieużywane w danym momencie deskryptory plików, ale czasami kod zapisuje bezpośrednio do deskryptora pliku 3, ja zgadnij (mógłbym sobie wyobrazić program sprawdzający deskryptor pliku, aby sprawdzić, czy jest otwarty, i używający go, jeśli jest, lub zachowujący się odpowiednio inaczej, jeśli nie jest). To drugie prawdopodobnie najlepiej jest pamiętać i stosować w przypadkach ogólnego zastosowania.
-bash: 3: Bad file descriptor.
Jeśli masz zainstalowany pakiet moreutils , możesz użyć narzędzia mispipe , które robi dokładnie to, o co prosiłeś.
Powyższe rozwiązanie lesmana można również wykonać bez nakładania się na uruchamianie zagnieżdżonych podprocesów, używając { .. }zamiast tego (pamiętając, że ta forma zgrupowanych poleceń zawsze musi kończyć się średnikami). Coś takiego:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Sprawdziłem ten konstrukt z wersją dash w wersji 0.5.5 i wersjami bash w wersji 3.2.25 i 4.2.42, więc nawet jeśli niektóre powłoki nie obsługują { .. }grupowania, nadal są zgodne z POSIX.
set -o pipefailin ksh lub dowolną liczbą posypanych waitpoleceń w obu. Myślę, że może to być po części problem z analizą składniową dla ksh, ponieważ jeśli będę trzymać się podpowłoki, to działa dobrze, ale nawet przy ifwyborze wariantu podpowłoki dla ksh, ale pozostawienie złożonych poleceń innym, to nie powiedzie się .
Jest to przenośne, tzn. Działa z dowolną powłoką zgodną z POSIX, nie wymaga zapisu katalogu bieżącego i umożliwia jednoczesne uruchomienie wielu skryptów przy użyciu tej samej sztuczki.
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
Edycja: tutaj jest silniejsza wersja po komentarzach Gillesa:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edycja2: i tutaj jest nieco lżejszy wariant po komentarzu dubiousjim:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Zgadzam się, że Bash jest łatwiejszy, ale w niektórych kontekstach warto wiedzieć, jak tego uniknąć.
Poniższe ma być dodatkiem do odpowiedzi @Patrik, na wypadek, gdybyś nie był w stanie skorzystać z jednego z typowych rozwiązań.
Ta odpowiedź zakłada:
$PIPESTATUSaniset -o pipefailDodatkowe założenia. Możesz się wszystkiego pozbyć, ale ten przepis zbyt mocno blokuje przepis, więc nie jest tu omawiany:
- Wszystko, co chcesz wiedzieć, to że wszystkie polecenia w PIPE mają kod wyjścia 0.
- Nie potrzebujesz dodatkowych informacji o paśmie bocznym.
- Twoja powłoka czeka na powrót wszystkich poleceń potoku.
Przed: foo | bar | bazjednak zwraca tylko kod wyjścia ostatniego polecenia ( baz)
Poszukiwany: $?nie może być 0(prawda), jeśli jakiekolwiek polecenie w potoku nie powiedzie się
Po:
TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"
{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
# $? now is 0 only if all commands had exit code 0
Wyjaśniono:
mktemp. Zwykle natychmiast tworzy plik/tmpwaitto potrzebne ksh, ponieważ kshinaczej nie czeka na zakończenie wszystkich poleceń potoku. Należy jednak pamiętać, że istnieją pewne niepożądane skutki uboczne, jeśli niektóre zadania w tle są obecne, więc domyślnie to skomentowałem. Jeśli czekanie nie zaszkodzi, możesz je skomentować.readzwraca false, trueoznacza to błądMoże to być użyte jako zamiennik wtyczki dla pojedynczego polecenia i wymaga tylko:
/proc/fd/NRobaki:
W tym skrypcie występuje błąd w przypadku /tmpbraku miejsca. Jeśli potrzebują ochrony przed tym sztucznym przypadku również można to zrobić w następujący sposób, to jednak ma tę wadę, że liczba 0IN 000zależy od liczby poleceń w rurze, więc jest to nieco bardziej skomplikowane:
TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"
{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
Uwagi dotyczące przenośności:
kshi podobne powłoki, które tylko czekają na ostatnie polecenie potoku, wymagają waitodkomentowania
Ostatni przykład używa printf "%1s" "$?"zamiast, echo -n "$?"ponieważ jest to bardziej przenośne. Nie każda platforma interpretuje -npoprawnie.
printf "$?"zrobiłby to również, ale printf "%1s"łapie niektóre przypadki narożne na wypadek, gdybyś uruchomił skrypt na naprawdę zepsutej platformie. (Przeczytaj: jeśli zdarzy ci się programować paranoia_mode=extreme.)
FD 8 i FD 9 mogą być wyższe na platformach obsługujących wiele cyfr. AFAIR powłoka zgodna z POSIX musi obsługiwać tylko pojedyncze cyfry.
Badano z Debiana 8.2 sh, bash, ksh, ash, sasha nawetcsh
Przy odrobinie ostrożności powinno to działać:
foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
Następujący blok „if” będzie działał tylko wtedy, gdy polecenie „komenda” się powiedzie:
if command; then
# ...
fi
Mówiąc konkretnie, możesz uruchomić coś takiego:
haconf_out=/path/to/some/temporary/file
if haconf -makerw > "$haconf_out" 2>&1; then
grep -iq "Cluster already writable" "$haconf_out"
# ...
fi
Który uruchomi się haconf -makerwi zapisze swoje stdout i stderr na „$ haconf_out”. Jeśli zwrócona wartość haconfjest prawdą, wówczas blok „if” zostanie wykonany i grepbędzie czytał „$ haconf_out”, próbując dopasować go do „Klastra już zapisywalnego”.
Zauważ, że rury automatycznie się oczyszczają; z przekierowaniem będziesz musiał ostrożnie usunąć „$ haconf_out” po zakończeniu.
Nie tak elegancki jak pipefail, ale uzasadniona alternatywa, jeśli ta funkcjonalność nie jest w zasięgu ręki.
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"
exec 8>&- 9>&-
{
{
{
{ #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8 # use exactly 1x prior to #END
#END
} 2>&1 | ${TEE} 1>&9
} 8>&1
} | exit $(read; printf "${REPLY}")
} 9>&1
exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$
(Przynajmniej bash) w połączeniu z set -ejednym można użyć podpowłoki, aby jawnie emulować pipefail i wyjść z błędu potoku
set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program
Jeśli więc fooz jakiegoś powodu zakończy się niepowodzeniem - reszta programu nie zostanie wykonana, a skrypt zakończy działanie z odpowiednim kodem błędu. (Zakłada się, że foodrukuje własny błąd, który wystarczy, aby zrozumieć przyczynę niepowodzenia)
EDYCJA : Ta odpowiedź jest błędna, ale interesująca, więc zostawię ją do wykorzystania w przyszłości.
!do polecenia powoduje odwrócenie kodu powrotu.
http://tldp.org/LDP/abs/html/exit-status.html
# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command # bash: bogus_command: command not found
echo $? # 127
! ls | bogus_command # bash: bogus_command: command not found
echo $? # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #
ls, a nie odwracać kod wyjściabogus_command