bash: jak propagować błędy w zastępowaniu procesów?

19

Chcę, aby moje skrypty powłoki kończyły się niepowodzeniem, ilekroć wykonanie polecenia nimi nie powiedzie się.

Zazwyczaj robię to z:

set -e
set -o pipefail

(zazwyczaj dodaję set -uteż)

Chodzi o to, że żadne z powyższych nie działa z podstawieniem procesu. Ten kod wypisuje „ok” i kończy z kodem powrotu = 0, podczas gdy chciałbym, aby się nie powiódł:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

Czy istnieje coś równoważnego z „pipefail” oprócz zamiany procesu? W jakikolwiek inny sposób przekazać do polecenia wynik polecenia, ponieważ były to pliki, ale zgłaszanie błędu za każdym razem, gdy któryś z tych programów zawiedzie?

Rozwiązaniem człowieka biednego byłoby wykrycie, czy te polecenia zapisują do stderr (ale niektóre polecenia zapisują do stderr w udanych scenariuszach).

Innym rozwiązaniem zgodnym z POSIX-em byłoby użycie nazwanych potoków, ale muszę wyliczyć te podstawienia poleceń, które używają procesu, ponieważ oneliniści budują w locie ze skompilowanego kodu, a tworzenie nazwanych potoków skomplikowałoby rzeczy (dodatkowe polecenia, błąd pułapkowania dla usuwanie ich itp.)

juanleon
źródło
Nie trzeba łapać błędów w celu usunięcia nazwanych potoków. W rzeczywistości nie jest to wcale dobry sposób. Zastanów się mkfifo pipe; { rm pipe; cat <file; } >pipe. Że komenda zawiśnie aż czytelnik otwiera pipeponieważ jest powłoka, która robi to open()i tak szybko, jak tam jest czytnik na pipefs odwołuje się do pipeIS rmD”, a następnie catkopiuje plik_wejściowy się deskryptorze przez powłokę do tej rury. W każdym razie, jeśli chcesz propagować błąd poza procesem sub: <( ! : || kill -2 "$$")
mikeserv
Dziękujemy za wprowadzenie dotyczące usuwania nazwanych potoków. Niestety, $$podstawienie nie działa dla mnie, ponieważ podstawienie polecenia nie jest wykonywane, ponieważ polecenie korzystające z podstawienia procesu odbywa się w potoku poleceń, który jest spawnowany z kodu „non-shell” (python). Prawdopodobnie powinienem utworzyć podproces w Pythonie i potokować je programowo.
juanleon
Więc użyj kill -2 0.
mikeserv
Niestety „kill -2 0” zabiłoby (sygnał) pytona. Pisanie procedur obsługi sygnałów do wykonywania logiki biznesowej w aplikacji wielowątkowej nie jest czymś, na co czekam :-)
juanleon
Jeśli nie chcesz obsługiwać sygnałów, dlaczego próbujesz je odbierać? W każdym razie, spodziewam się, że nazwane potoki byłyby w końcu najprostszym rozwiązaniem, choćby dlatego, że robienie tego poprawnie wymaga opracowania własnego frameworka i skonfigurowania własnego, spersonalizowanego programu rozsyłającego. Pokonaj tę przeszkodę i wszystko się połączy.
mikeserv

Odpowiedzi:

6

Można obejść ten problem tylko na przykład:

cat <(false || kill $$) <(echo ok)
other_command

Warstwa wewnętrzna skryptu znajduje się SIGTERMd przed wykonaniem drugiego polecenia ( other_command). echo okPolecenie jest wykonywane „czasami”: Problemem jest to, że substytucje procesowe są asynchroniczne. Nie ma gwarancji, że kill $$polecenie zostanie wykonane przed lubecho ok poleceniem po nim . Jest to kwestia planowania systemów operacyjnych.

Rozważmy taki skrypt bash:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

Dane wyjściowe tego skryptu mogą być:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

Lub:

$ ./script
Terminated
$ pre
post

$ echo $?
143

Możesz spróbować, a po kilku próbach zobaczysz dwa różne zamówienia na wyjściu. W pierwszym skrypt został zakończony, zanim pozostałe dwie echokomendy mogły zapisać do deskryptora pliku. W drugimfalse lub killkomenda prawdopodobnie zostały zaplanowane po echopoleceń.

Lub być bardziej precyzyjnie: Wywołanie systemowe signal()z killutillity który wysyła TheSIGTERM zaplanowano sygnał do procesu muszli (lub została dostarczona) później lub wcześniej niż echo write()wywołań systemowych.

Jednak skrypt zatrzymuje się, a kod wyjścia nie ma wartości 0. Dlatego powinno rozwiązać problem.

Inne rozwiązanie jest oczywiście użycie do tego nazwanych potoków. Zależy to jednak od tego, jak skomplikowane byłoby wdrożenie nazwanych potoków lub powyższego obejścia.

Bibliografia:

chaos
źródło
2

Dla przypomnienia, nawet jeśli odpowiedzi i komentarze były dobre i pomocne, zakończyłem wdrażanie czegoś nieco innego (miałem pewne ograniczenia dotyczące odbierania sygnałów w procesie nadrzędnym, o których nie wspomniałem w pytaniu)

Zasadniczo skończyłem robić coś takiego:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Następnie sprawdzam plik błędu. Jeśli istnieje, wiem, która komenda nie powiodła się (a zawartość pliku_błędu może być przydatna). Bardziej gadatliwy i zrzędliwy, niż początkowo chciałem, ale mniej kłopotliwy niż tworzenie nazwanych potoków w poleceniu bash jednoliniowym.

juanleon
źródło
Cokolwiek działa dla Ciebie, jest zwykle najlepszym sposobem. Dziękuję zwłaszcza za powrót i odpowiadanie - selfie są moimi ulubionymi.
mikeserv
2

Ten przykład pokazuje, jak używać killrazem z trap.

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

Ale killnie można przekazać kodu powrotu z procesu podrzędnego do procesu nadrzędnego.

Ceving
źródło
I jakby tego wariantu na Zaakceptowanych odpowiedź
sehe
1

W podobny sposób, jak zaimplementujesz $PIPESTATUS/ $pipestatusz powłoką POSIX, która jej nie obsługuje , możesz uzyskać status wyjścia poleceń, przekazując je za pomocą potoku:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

Co daje:

ok
cat_code=0
false_code=1
echo_code=0

Możesz też pipefailręcznie zastosować i zaimplementować podstawianie procesów, tak jak w przypadku powłok, które go nie obsługują:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
Stéphane Chazelas
źródło