Obecnie mam skrypt, który robi coś takiego
./a | ./b | ./c
Chcę go zmodyfikować, aby w przypadku wyjścia a, b lub c z kodem błędu wydrukowałem komunikat o błędzie i zatrzymałem się zamiast przesyłać do przodu złe wyjście.
Jaki byłby najprostszy / najczystszy sposób, aby to zrobić?
shell
error-handling
pipe
hugomg
źródło
źródło
&&|
co oznaczałoby „kontynuowanie potoku tylko wtedy, gdy poprzednie polecenie się powiodło”. Przypuszczam, że mógłbyś również mieć,|||
co oznaczałoby „kontynuuj potok, jeśli poprzednie polecenie zawiodło” (i prawdopodobnie potokuj komunikat o błędzie jak Bash 4|&
).a
,b
,c
polecenia nie są uruchamiane sekwencyjnie, ale równolegle. Innymi słowy, dane płynie kolejno oda
doc
, ale rzeczywistea
,b
ic
zacząć polecenia (w przybliżeniu) w tym samym czasie.Odpowiedzi:
Jeśli naprawdę nie chcesz, aby drugie polecenie było kontynuowane, dopóki pierwsze nie zakończy się sukcesem, prawdopodobnie musisz użyć plików tymczasowych. Prosta wersja to:
Przekierowanie „1> & 2” można również skrócić „> & 2”; jednak stara wersja powłoki MKS błędnie radziła sobie z przekierowaniem błędu bez poprzedzającej je cyfry „1”, więc od wieków używam tej jednoznacznej notacji dla zapewnienia niezawodności.
To powoduje wyciek plików, jeśli coś przerwiesz. Programowanie pocisków odpornych na bomby (mniej lub bardziej) wykorzystuje:
Pierwsza linia pułapki mówi „uruchom komendy”
rm -f $tmp.[12]; exit 1
, gdy wystąpi którykolwiek z sygnałów 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE lub 15 SIGTERM, lub 0 (gdy powłoka kończy pracę z dowolnego powodu). Jeśli piszesz skrypt powłoki, ostatnia pułapka musi tylko usunąć pułapkę na 0, która jest pułapką wyjściową powłoki (możesz pozostawić inne sygnały na miejscu, ponieważ proces i tak wkrótce się zakończy).W pierwotnym potoku możliwe jest, że „c” będzie czytać dane z „b” przed zakończeniem „a” - jest to zwykle pożądane (na przykład daje wiele rdzeni do wykonania). Jeśli „b” jest fazą „sortowania”, to nie ma to zastosowania - „b” musi zobaczyć wszystkie swoje dane wejściowe, zanim będzie mógł wygenerować jakiekolwiek dane wyjściowe.
Jeśli chcesz wykryć, które polecenia zawodzą, możesz użyć:
Jest to proste i symetryczne - trywialne jest rozszerzenie do potoku 4-częściowego lub N-częściowego.
Proste eksperymentowanie z „set -e” nie pomogło.
źródło
mktemp
lubtempfile
.trap
należy pamiętać, że użytkownik zawsze może wysłaćSIGKILL
do procesu natychmiastowe zakończenie, w którym to przypadku pułapka nie zadziała. To samo dotyczy sytuacji, gdy w systemie wystąpi awaria zasilania. Podczas tworzenia plików tymczasowych upewnij się, że używasz,mktemp
ponieważ spowoduje to umieszczenie plików gdzieś, gdzie zostaną wyczyszczone po ponownym uruchomieniu (zwykle do/tmp
).W bashu możesz użyć
set -e
iset -o pipefail
na początku pliku. Kolejne polecenie./a | ./b | ./c
zakończy się niepowodzeniem, gdy zawiedzie którykolwiek z trzech skryptów. Kod powrotu będzie kodem powrotu pierwszego skryptu, który zakończył się niepowodzeniem.Pamiętaj, że
pipefail
nie jest dostępny w standardowym sh .źródło
set
(łącznie dla 4 różnych wersji) i zobaczenie, jak to wpływa na wynik ostatniejecho
.Możesz też sprawdzić
${PIPESTATUS[]}
tablicę po pełnym wykonaniu, np. Jeśli uruchomisz:Następnie
${PIPESTATUS}
pojawi się tablica kodów błędów z każdego polecenia w potoku, więc jeśli środkowe polecenie zawiodło,echo ${PIPESTATUS[@]}
zawierałoby coś takiego:i coś takiego wykonujemy po poleceniu:
pozwoli ci sprawdzić, czy wszystkie polecenia w potoku powiodły się.
źródło
#!/bin/sh
, ponieważ jeślish
nie jest to bash, to nie zadziała. (Łatwe do naprawienia, pamiętając o używaniu#!/bin/bash
zamiast tego.)echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'
- zwraca 0, jeśli$PIPESTATUS[@]
zawiera tylko 0 i spacje (jeśli wszystkie polecenia w potoku się powiodły).command1 && command2 | command3
Jeśli którykolwiek z nich zawiedzie, twoje rozwiązanie zwraca wartość niezerową.Niestety, odpowiedź Johnathana wymaga plików tymczasowych, a odpowiedzi Michela i Imrona wymagają basha (mimo że to pytanie jest oznaczone jako powłoka). Jak już zauważyli inni, nie jest możliwe przerwanie potoku przed rozpoczęciem późniejszych procesów. Wszystkie procesy są uruchamiane od razu i dlatego będą działać, zanim będzie można zgłosić jakiekolwiek błędy. Ale tytuł pytania dotyczył również kodów błędów. Można je odzyskać i zbadać po zakończeniu rury, aby ustalić, czy którykolwiek z zaangażowanych procesów zawiódł.
Oto rozwiązanie, które wyłapuje wszystkie błędy w rurze, a nie tylko błędy ostatniego komponentu. Tak to jest jak pipefail atakujących, tuż mocniejsze w tym sensie, że można odzyskać wszystkie kody błędów.
Aby wykryć, czy coś nie powiodło,
echo
polecenie wypisuje na standardowym wyjściu błędów w przypadku dowolnego polecenia nie powiedzie się. Następnie połączone standardowe wyjście błędów jest zapisywane$res
i analizowane później. Dlatego też standardowy błąd wszystkich procesów jest przekierowywany na standardowe wyjście. Możesz również wysłać te dane wyjściowe/dev/null
lub pozostawić je jako kolejny wskaźnik, że coś poszło nie tak. Możesz zastąpić ostatnie przekierowanie/dev/null
plikiem, jeśli nie chcesz przechowywać danych wyjściowych ostatniego polecenia w dowolnym miejscu.Aby grać więcej z tego konstruktu i przekonać się, że to naprawdę robi to, co powinien, wymieniłem
./a
,./b
a./c
przez podpowłok, które wykonująecho
,cat
orazexit
. Możesz użyć tego, aby sprawdzić, czy ta konstrukcja rzeczywiście przekazuje wszystkie dane wyjściowe z jednego procesu do drugiego i czy kody błędów są poprawnie rejestrowane.źródło