Uzyskaj stan zakończenia procesu, który ' jest przesłany potokiem do innego

Mam dwa procesy foo i bar, połączone rurą:

$ foo | bar 

bar zawsze kończy 0; Interesuje mnie kod zakończenia foo. Czy jest jakiś sposób, aby to osiągnąć?

Komentarze

Answer

bash i zsh mają zmienną tablicową który przechowuje status wyjścia każdego elementu (polecenia) ostatniego potoku wykonanego przez powłokę.

Jeśli używasz bash, tablica nosi nazwę PIPESTATUS (wielkość liter ma znaczenie!), a wskazania tablicy zaczynają się od zera:

$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0 

Jeśli używasz zsh, tablica nazywa się (wielkość liter ma znaczenie!), a indeksy tablicy zaczynają się od jednego:

$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0 

Aby połączyć je w 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 bash lub zsh i „uzyskasz te same wyniki; tylko jeden z retval_bash i retval_zsh zostanie ustawiony. Drugi będzie pusty. Umożliwiłoby to zakończenie funkcji na return $retval_bash $retval_zsh (zwróć uwagę na brak cudzysłowów!).

Komentarze

  • I w zsh. Niestety inne powłoki nie ' nie mają tej funkcji.
  • Uwaga: tablice w zsh zaczynają się sprzecznie z intuicją od indeksu 1, więc ' s echo "$pipestatus[1]" "$pipestatus[2]".
  • Możesz sprawdzić cały potok w ten sposób: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @JanHudec: Może powinieneś przeczytać pierwsze pięć słów mojej odpowiedzi. Uprzejmie proszę również wskazać, gdzie pytanie wymagało odpowiedzi tylko w standardzie POSIX.
  • @JanHudec: Nie było też oznakowane jako POSIX. Dlaczego zakładasz, że odpowiedzią musi być POSIX? Nie zostało to określone, więc udzieliłem kwalifikowanej odpowiedzi. Moja odpowiedź nie jest nieprawidłowa, poza tym istnieje wiele odpowiedzi dotyczących innych przypadków.

Odpowiedź

Tutaj są 3 typowe sposoby zrobienia tego:

Pipefail

Pierwszym sposobem jest ustawienie opcji pipefail (ksh, zsh lub bash). To jest najprostsze i to, co robi, to po prostu ustawienie statusu zakończenia $? na kod zakończenia ostatniego programu, aby zakończyć pracę niezerową (lub zero, jeśli wszystko zakończyło się pomyślnie).

$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1 

$ PIPESTATUS

Bash ma również zmienną tablicową o nazwie $PIPESTATUS ($pipestatus w zsh), który zawiera stan 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ć konkretną wartość w potoku, której potrzebujesz.

Oddzielne wykonania

Jest to najbardziej nieporęczne rozwiązanie. Uruchom każdą komendę osobno i zapisz jej status

$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1 

Komentarze

  • Cholera! Właśnie miałem napisać o PIPESTATUS.
  • Dla odniesienia jest kilka innych technik omówionych w tym pytaniu SO: stackoverflow.com/questions/1221833/…
  • @Patrick rozwiązanie pipestatus działa na bash, tylko więcej quastion w przypadku, gdy używam skryptu ksh. Myślisz, że możemy znaleźć coś podobnego do pipestatus? , (chociaż widzę, że pipestatus nie jest obsługiwany przez ksh)
  • @yael Nie ' nie używam ksh, ale po krótkim spojrzeniu na ' stronę podręcznika, nie ' nie obsługuje $PIPESTATUS lub coś podobnego. Obsługuje jednak opcję pipefail.
  • Zdecydowałem się na pipefail, ponieważ pozwala mi uzyskać status nieudanego polecenia tutaj: LOG=$(failed_command | successful_command)

Odpowiedź

To rozwiązanie działa bez korzystania z funkcji bash lub plików tymczasowych . Bonus: w końcu status wyjścia jest w rzeczywistości statusem wyjścia, a nie jakimś ciągiem znaków w pliku.

Sytuacja:

someprog | filter 

ty chcę uzyskać status wyjścia z someprog i wyjście z filter.

Oto moje rozwiązanie:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

Wynik tej konstrukcji to standardowe wyjście z filter jako standardowe wyjście konstrukcji i stan wyjścia z someprog jako kod zakończenia konstrukcji.


ta konstrukcja działa również z prostym grupowaniem poleceń {...} zamiast podpowłok (...). podpowłoki mają pewne konsekwencje, między innymi koszt wydajności, którego tutaj nie potrzebujemy. przeczytaj dokładną instrukcję basha, 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 basha wymaga spacji i średników w nawiasach klamrowych, więc konstrukcja staje się dużo bardziej obszerna.

W dalszej części tego tekstu użyję wariantu podpowłoki.


Przykład someprog i 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 deskryptory otwartych plików z rodzica. Oznacza to, że someprog odziedziczy otwarte deskryptory pliku 3 i 4. Jeśli someprog zapisuje do deskryptora pliku 3, to stanie się to kodem zakończenia. Rzeczywisty stan wyjścia zostanie zignorowany, ponieważ read odczytuje tylko raz.

Jeśli obawiasz się, że someprog może napisać 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 

exec 3>&- 4>&- przed someprog zamyka deskryptor pliku przed wykonaniem someprog, więc dla someprog te deskryptory plików po prostu nie istnieją.

Można to również zapisać w ten sposób: someprog 3>&- 4>&-


Wyjaś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:

  1. Tworzona jest podpowłoka z deskryptorem pliku 4 przekierowany na stdout. Oznacza to, że cokolwiek zostanie wydrukowane w deskryptorze pliku 4 w podpowłoce, skończy jako standardowe wyjście całej konstrukcji.
  2. Tworzony jest potok i polecenia po lewej stronie (#part3) i prawy (#part2) są wykonywane. exit $xs jest również ostatnim poleceniem potoku, co oznacza, że ciąg ze standardowego wejścia będzie kodem zakończenia całej konstrukcji.
  3. Podpowłoka jest tworzona za pomocą pliku deskryptor 3 przekierowany na standardowe wyjście. Oznacza to, że cokolwiek zostanie wydrukowane do deskryptora pliku 3 w tej podpowłoce, skończy w #part2 i z kolei będzie kodem zakończenia całej konstrukcji.
  4. A potok jest tworzony, a polecenia po lewej (#part5 i #part6) i po prawej (filter >&4) są wykonywane. Wynik filter jest przekierowywany do deskryptora pliku 4. W #part1 deskryptor pliku 4 został przekierowany na standardowe wyjście. Oznacza to, że wyjście filter jest standardowym wyjściem całej konstrukcji.
  5. Status wyjścia z #part6 jest drukowany do deskryptora pliku 3. W #part3 deskryptor pliku 3 został przekierowany do #part2. Oznacza to, że stan wyjścia z #part6 będzie ostatecznym kodem wyjścia dla całej konstrukcji.
  6. someprog to wykonany. Stan wyjścia jest pobierany w #part5. Standardowe wyjście jest pobierane przez potok w #part4 i przekazywane do filter. Dane wyjściowe z filter z kolei osiągną standardowe wyjście, jak wyjaśniono w #part4

Komentarze

  • Świetnie. W przypadku funkcji, którą mogę po prostu zrobić, (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) upraszcza do someprog 3>&- 4>&-.
  • Ta metoda działa również bez podpowłok: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Odpowiedź

Chociaż nie jest to dokładnie to, o co prosiłeś, możesz użyć

#!/bin/bash -o pipefail 

, aby twoje potoki zwracały ostatni niezerowy powrót.

może być trochę 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 

Komentarze

  • set -o pipefail wewnątrz skryptu powinno być bardziej niezawodne, np. na wypadek, gdyby ktoś wykonał skrypt przez bash foo.sh.
  • Jak to działa? masz przykład?
  • Zauważ, że -o pipefail nie jest w POSIX.
  • To nie działa w moim BASH 3.2.25 ( 1) – wydanie. Na górze / tmp / ff mam #!/bin/bash -o pipefail.Błąd: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: Niektóre środowiska (w tym Linux) nie ' t analizują spacje w #! poza pierwszą, więc staje się to /bin/bash -o pipefail /tmp/ff, zamiast niezbędnego /bin/bash -o pipefail /tmp/ffgetopt (lub podobnego) parsowanie przy użyciu optarg, który jest następnym elementem w ARGV, jako argument -o, więc kończy się niepowodzeniem. Gdybyś utworzył opakowanie (powiedzmy bash-pf, które właśnie spowodowało exec /bin/bash -o pipefail "$@", i umieściło je na #!, to zadziała. Zobacz też: en.wikipedia.org/wiki/Shebang_%28Unix%29

Odpowiedź

Jeśli to możliwe, podaję kod zakończenia z foo do bar. Na przykład, jeśli wiem, że foo nigdy nie tworzy linii zawierającej same cyfry, mogę po prostu przypiąć kod zakończenia:

{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}" 

Lub jeśli wiem, że dane wyjściowe z foo nigdy nie zawierają wiersza zawierającego tylko .:

{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}" 

Zawsze można to zrobić, jeśli istnieje sposób na uzyskanie bar do pracy ze wszystkimi wierszami oprócz ostatniej i przekazanie ostatniego wiersza jako kodu wyjścia.

Jeśli bar jest złożonym potokiem, którego wyjście nie potrzebujesz, możesz ominąć część, drukując kod zakończenia na innym deskryptorze pliku.

exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1) 

Po tym $exit_codes to zwykle foo:X bar:Y, ale może to być bar:Y foo:X, jeśli bar kończy pracę przed przeczytaniem wszystkich jego danych wejściowych lub jeśli masz pecha. Wydaje mi się, że zapisy do potoków o rozmiarze do 512 bajtów są niepodzielne we wszystkich jednostkach, więc foo:$? i bar:$? części nie będą mieszane jako jeśli ciągi znaczników mają mniej niż 507 bajtów.

Jeśli chcesz przechwycić dane wyjściowe z bar, staje się to trudne. Możesz łączyć powyższe techniki, aby wynik bar nigdy nie zawierał wiersza, który wygląda jak wskazanie kodu zakończenia, ale robi się dziwnie.

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 przy użyciu pliku tymczasowego do przechowywania statusu. Prosty, ale nie taki prosty w produkcji:

  • Jeśli równolegle działa wiele skryptów lub jeśli ten sam skrypt używa tej metody w kilku miejscach, należy upewnij się, że używają różnych nazw plików tymczasowych.
  • Bezpieczne utworzenie pliku tymczasowego w katalogu współdzielonym jest trudne. Często /tmp jest jedynym miejscem, w którym skrypt z pewnością będzie w stanie zapisywać pliki. Użyj mktemp , który nie jest zgodny z POSIX, ale jest obecnie dostępny we wszystkich poważnych unicach.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Komentarze

  • Korzystając z metody plików tymczasowych, wolę dodać pułapkę na EXIT, która usuwa wszystkie pliki tymczasowe aby żadne śmieci nie pozostały, nawet jeśli skrypt umrze

Odpowiedź

Zaczynając od potoku:

foo | bar | baz 

Oto ogólne rozwiązanie używające tylko powłoki POSIX i żadnych 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 stanu wszystkich zakończonych niepowodzeniem procesów, w kolejności losowej, z indeksami informującymi, które polecenie wyemitowało dany stan.

# 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 cudzysłowy wokół $error_statuses w moich testach; bez nich grep nie może „t rozróżniać, ponieważ znaki nowej linii są przekształcane w spacje.

Odpowiedź

Jeśli masz zainstalowany pakiet moreutils , możesz użyć narzędzia mispipe , które robi dokładnie to, o co prosiłeś.

Odpowiedź

Więc chciałem wnieść odpowiedź jak lesmana „s, ale myślę, że moja jest być może trochę prostsza i nieco bardziej korzystna. Rozwiązanie powłoki Bournea:

# 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 podszewki – polecenie1 wykona i wydrukuje swoje zwykłe wyjście na standardowe wyjście (deskryptor pliku 1) , po zakończeniu printf wykona i wypisze kod wyjścia polecenia1 na swoje standardowe wyjście, ale to standardowe wyjście jest przekierowywane do deskryptora pliku 3.

Gdy polecenie1 jest uruchomione, jego standardowe wyjście jest przesyłane potokiem do polecenie2 (wyjście printf nigdy nie dociera do polecenia2, ponieważ wysyłamy je do deskryptora pliku 3 zamiast 1, co i co czyta potok).Następnie przekierowujemy wyjście polecenia2 do deskryptora pliku 4, aby również pozostawało poza deskryptorem pliku 1 – ponieważ chcemy, aby deskryptor pliku 1 był wolny trochę później, ponieważ przeniesiemy wynik printf z deskryptora pliku 3 z powrotem do deskryptor pliku 1 – ponieważ „to właśnie przechwyci podstawienie polecenia (lewy apostrof) i to” zostanie umieszczone w zmiennej.

Ostatnią magią jest to, że najpierw exec 4>&1 zrobiliśmy jako osobne polecenie – otwiera deskryptor pliku 4 jako kopię standardowego wyjścia zewnętrznej powłoki. Podstawianie polecenia przechwyci wszystko, co jest zapisane w standardzie, z perspektywy poleceń w nim zawartych – ale ponieważ wynik polecenia2 trafia do deskryptora pliku 4, jeśli chodzi o podstawianie poleceń, podstawianie poleceń go nie przechwytuje – jednakże, gdy „wyjdzie” z podstawiania poleceń, skutecznie nadal przechodzi do ogólnego deskryptora pliku 1.

(exec 4>&1 ma być oddzielnym poleceniem, ponieważ wiele typowych powłok nie lubi tego, gdy próbujesz pisać do deskryptora pliku wewnątrz polecenia podstawienia, które jest otwierane w poleceniu „zewnętrznym” używającym podstawienia. Więc to jest najprostszy przenośny sposób aby to zrobić.)

Możesz spojrzeć na to w mniej techniczny i bardziej zabawny sposób, tak jakby dane wyjściowe poleceń przeskakiwały wzajemnie: polecenie1 przeskakuje do polecenia2, a następnie wyjście printf przeskakuje ponad polecenie 2, aby polecenie 2 go nie przechwytywało, a następnie wyjście polecenia 2 przeskakuje nad i poza Podstawianie polecenia tak, jak printf ląduje w samą porę, aby zostać przechwycone przez podstawienie, tak że kończy się w zmiennej, a wyjście polecenia2 jest w wesoły sposób zapisywane na standardowe wyjście, tak jak w zwykłym potoku.

Ponadto, jak rozumiem, $? nadal będzie zawierał kod powrotu drugiego polecenia w potoku, ponieważ przypisania zmiennych, podstawienia poleceń i polecenia złożone są wszystkie efektywnie przezroczyste dla kodu zwrotnego polecenia w nich zawartego, więc status powrotu polecenia2 powinien zostać rozpropagowany – to i brak konieczności definiowania dodatkowej funkcji, dlatego myślę, że może to być nieco lepsze rozwiązanie niż proponowane przez lesmana.

Zgodnie z zastrzeżeniami, o których wspomina Lesmana, jest możliwe, że polecenie1 w pewnym momencie skończy używać deskryptorów plików 3 lub 4, więc aby być bardziej niezawodnym, zrobiłbyś:

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żywając ( ) zamiast { } również będzie działać, chociaż 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 cztery, a polecenie złożone, po którym następuje 3>&1, odziedziczy deskryptor pliku trzeci. Dlatego 4>&- zapewnia, że wewnętrzne polecenie złożone nie odziedziczy czwartego deskryptora pliku, a 3>&- nie odziedziczy trzeciego deskryptora pliku, więc polecenie1 otrzymuje „czystsze”, bardziej standardowe środowisko. Możesz także przenieść wewnętrzny element 4>&- obok elementu 3>&-, ale myślę, że dlaczego nie ograniczyć jego zakresu tak bardzo, jak to możliwe.

Nie jestem pewien, jak często rzeczy używają bezpośrednio deskryptorów pliku 3 i 4 – myślę, że większość programów używa wywołań systemowych, które zwracają deskryptory plików nieużywanych w danym momencie, ale czasami kod zapisuje do pliku Wydaje mi się, że deskryptor 3 bezpośrednio (mógłbym sobie wyobrazić program sprawdzający deskryptor pliku, aby sprawdzić, czy jest otwarty, i używający go, jeśli jest otwarty, lub zachowujący się inaczej, jeśli nie jest). Więc ten drugi prawdopodobnie najlepiej w pamięci i do zastosowań ogólnych.

Komentarze

  • Wygląda interesująco, ale mogę ' Nie wiem, czego oczekujesz od tego polecenia, a mój komputer może też ' t. Otrzymuję -bash: 3: Bad file descriptor.
  • @ G-Man Racja, ciągle zapominam, że bash nie ma pojęcia, co ' robi, kiedy mes do deskryptorów plików, w przeciwieństwie do powłok, których zwykle używam (popiół dostarczany z busyboxem). ' Poinformuję Cię, gdy pomyślę o obejściu, które uszczęśliwia bash. W międzyczasie, jeśli ' masz pod ręką skrzynkę Debiana, możesz wypróbować ją w myślniku lub jeśli ' masz pod ręką busybox można spróbować z busybox ash / sh.
  • @ G-Man Jeśli chodzi o to, czego oczekuję od polecenia i co robi w innych powłokach, to przekierowuje standardowe wyjście z polecenia1, więc nie ' nie daj się złapać przez podstawianie poleceń, ale po wyjściu z podstawiania poleceń spada fd3 z powrotem na stdout, więc ' s potokował zgodnie z oczekiwaniami do polecenia2.Kiedy polecenie1 kończy działanie, printf odpala i wyświetla swój kod wyjścia, który jest przechwytywany do zmiennej przez podstawianie polecenia. Bardzo szczegółowe zestawienie tutaj: stackoverflow.com/questions/985876/tee-and-exit-status/… Również , twój komentarz brzmiał, jakby miał być trochę obraźliwy?
  • Od czego mam zacząć? (1) Przepraszam, jeśli poczułeś się urażony. „Wygląda interesująco” miał poważnie; byłoby wspaniale, gdyby coś tak zwartego działało tak dobrze, jak się tego spodziewałeś. Poza tym mówiłem po prostu, że nie rozumiem, co ma robić twoje rozwiązanie. Pracuję / bawię się z Uniksem przez długi czas (zanim powstał Linux) i, jeśli czegoś nie rozumiem, jest to czerwona flaga, która może inni ludzie też tego nie zrozumieją i że wymaga to więcej wyjaśnień (IMNSHO). … (Ciąg dalszy)
  • (ciąg dalszy)… Ponieważ ty „… chciałbym pomyśleć… że [ty] rozumiesz prawie wszystko więcej niż przeciętna osoba ”, może powinieneś pamiętać, że celem wymiany stosów nie jest bycie usługą pisania poleceń, dostarczającą tysiące jednorazowych rozwiązań banalnie odmiennych pytań; ale raczej uczyć ludzi, jak rozwiązywać własne problemy. I w tym celu może musisz wyjaśnić rzeczy na tyle dobrze, aby „przeciętny człowiek” mógł to zrozumieć. Spójrz na odpowiedź Lesmany, aby zobaczyć przykład doskonałego wyjaśnienia. … (Ciąg dalszy)

Odpowiedź

lesmana „powyższe rozwiązanie można również wykonać bez narzutu rozpoczynanie zagnieżdżonych podprocesów przy użyciu 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 tę konstrukcję z wersją myślnika 0.5.5 i bash w wersjach 3.2.25 i 4.2.42, więc nawet jeśli niektóre powłoki nie obsługują { .. } grupowanie, jest nadal zgodne z POSIX.

Komentarze

  • Działa to bardzo dobrze z większością powłok I ' już go wypróbował, w tym NetBSD sh, pdksh, mksh, dash, bash. Jednak mogę ' t zmusić go do pracy z AT & T Ksh (93s +, 93u +) lub zsh (4.3.9, 5.2), nawet z set -o pipefail w ksh lub dowolnej liczbie posypanych wait w obu. Myślę, że może, częściowo przynajmniej, bądź problemem z analizą ksh, tak jakbym trzymał się podpowłok, to działa dobrze, ale nawet z if, aby wybrać wariant podpowłoki dla ksh, ale zostaw komendy złożone dla innych nie udaje się.

Odpowiedź

Poniższe ma być dodatkiem do odpowiedzi @Patrik, na wypadek, gdybyś nie był w stanie użyć jednego z typowych rozwiązań.

Ta odpowiedź zakłada, że:

  • Masz powłokę, która nie wie o $PIPESTATUS ani set -o pipefail
  • Chcesz użyć potoku do wykonywania równoległego, więc nie ma plików tymczasowych.
  • Musisz nie chcą mieć dodatkowego bałaganu, jeśli przerwiesz skrypt, być może przez nagłą przerwę w zasilaniu.
  • To rozwiązanie powinno być stosunkowo łatwe do naśladowania i przejrzyste do czytania.
  • Tak nie chcesz wprowadzać dodatkowych podpowłok.
  • Nie możesz majstrować przy istniejących deskryptorach plików, więc nie wolno dotykać stdin / out / err (jak kiedykolwiek możesz tymczasowo wprowadzić jakąś nową)

Dodatkowe założenia. Możesz się ich pozbyć, ale to zbytnio utrudnia przepis, więc nie jest tutaj omawiany:

  • Wszystko, co chcesz wiedzieć, to to, że wszystkie polecenia w PIPE mają kod zakończenia 0.
  • Nie potrzebujesz dodatkowych informacji o wstęgach bocznych.
  • Twoja powłoka czeka na zwrócenie wszystkich poleceń potoków.

Przed: foo | bar | baz, jednak zwraca to tylko kod zakończenia ostatniego polecenia (baz)

Poszukiwane: $? nie może być 0 (prawda), jeśli którekolwiek z poleceń w potoku nie powiodło 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śnienie:

  • Plik tymczasowy jest tworzony za pomocą mktemp. Zwykle powoduje to natychmiastowe utworzenie pliku w /tmp
  • Ten tymczasowy plik jest następnie przekierowywany do FD 9 do zapisu i FD 8 do odczytu
  • Następnie tempfile jest natychmiast usuwany. Pozostaje jednak otwarty, dopóki nie znikną oba FD.
  • Teraz potok jest uruchomiony. Każdy krok jest dodawany tylko do FD 9, jeśli wystąpił błąd.
  • Element wait jest potrzebny dla ksh, ponieważ ksh w przeciwnym razie nie czeka na zakończenie wszystkich poleceń potoków. Należy jednak pamiętać, że istnieją niepożądane efekty uboczne, jeśli obecne są niektóre zadania w tle, więc domyślnie to skomentowałem.Jeśli oczekiwanie nie zaszkodzi, możesz to skomentować.
  • Następnie zawartość pliku jest czytana. Jeśli jest pusty (ponieważ wszystko działało) read zwraca false, więc true wskazuje błąd

Można tego użyć jako zamiennika wtyczki jedno polecenie i wystarczy:

  • Nieużywane FD 9 i 8
  • Pojedyncza zmienna środowiskowa do przechowywania nazwy pliku tymczasowego
  • A to receptura może być dostosowana do dowolnej powłoki, która umożliwia przekierowanie IO
  • Ponadto jest dość niezależna od platformy i nie potrzebuje takich rzeczy jak /proc/fd/N

BŁĘDY:

W tym skrypcie występuje błąd, na wypadek gdyby /tmp zabrakło miejsca. Jeśli potrzebujesz ochrony również przed tym sztucznym przypadkiem, możesz to zrobić w następujący sposób, jednak ma to tę wadę, że liczba 0 w 000 zależy od liczby poleceń wpipe, 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:

  • ksh i podobne powłoki, które czekają tylko na ostatnie polecenie potoku, wymagają wait bez komentarzy

  • Ostatni przykład używa printf "%1s" "$?" zamiast echo -n "$?", ponieważ jest to bardziej przenośne. Nie każda platforma poprawnie interpretuje -n.

  • printf "$?" też by to zrobił, jednak printf "%1s" wychwytuje pewne przypadki w przypadku uruchomienia skryptu na jakiejś naprawdę zepsutej platformie. (Przeczytaj: jeśli zdarzy ci się programować w paranoia_mode=extreme.)

  • FD 8 i FD 9 mogą być wyższe na platformach obsługujących wiele cyfry. AFAIR powłoka zgodna z POSIX musi obsługiwać tylko pojedyncze cyfry.

  • Testowano w Debianie 8.2 sh, bash, ksh, ash, sash, a nawet csh

Odpowiedź

To jest przenośne, tzn. działa z każdą powłoką zgodną z POSIX, nie wymaga możliwości zapisu w bieżącym katalogu i umożliwia jednoczesne uruchamianie wielu skryptów używających tej samej sztuczki.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$)) 

Edycja: tutaj jest mocniejsza wersja po komentarzach Gillesa:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s)) 

Edit2: a tutaj jest nieco lżejsza wersja po wątpliwym komentarzu jima:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)) 

Komentarze

  • To nie ' nie działa z kilku powodów. 1. Plik tymczasowy można odczytać, zanim zostanie ' zapisany. 2. Tworzenie pliku tymczasowego w katalogu współdzielonym o przewidywalnej nazwie jest niebezpieczne (trywialne DoS, wyścig dowiązań symbolicznych). 3. Jeśli ten sam skrypt używa tej sztuczki kilka razy, ' zawsze będzie używał tej samej nazwy pliku. Aby rozwiązać 1, przeczytaj plik po zakończeniu potoku. Aby rozwiązać 2 i 3, użyj pliku tymczasowego o losowo wygenerowanej nazwie lub w prywatnym katalogu.
  • +1 Cóż, $ {PIPESTATUS [0]} jest łatwiejsze, ale podstawowa idea działa, jeśli wiadomo o problemach, o których wspomina Gilles.
  • Możesz zapisać kilka podpowłok: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Zgadzam się, że ' jest łatwiejsze z Bash, ale w niektórych kontekstach warto wiedzieć, jak go unikać.

Odpowiedź

Przy odrobinie ostrożności powinno to zadziałać:

foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status) 

Komentarze

  • Co powiesz na porządkowanie jak jlliagre? Nie ' czy nie zostawiasz za sobą pliku o nazwie foo-status?
  • @Johan: Jeśli wolisz moją sugestię, nie ' nie wahaj się zagłosować;) Oprócz tego, że nie opuszcza pliku, ma tę zaletę, że pozwala wielu procesom uruchamiać go jednocześnie, a bieżący katalog nie musi być zapisywalny.

Odpowiedź

Poniższy blok „if” zostanie uruchomiony tylko wtedy, gdy polecenie „command” 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 będzie działał haconf -makerw i zapisz jego stdout i stderr do „$ haconf_out”. Jeśli wartość zwrócona z haconf to prawda, wówczas blok „if” zostanie wykonany i grep odczyta „$ haconf_out”, próbując aby dopasować go do „Klaster już zapisywalny”.

Zauważ, że potoki automatycznie się oczyszczają; z przekierowaniem „będziesz musiał uważać, aby usunąć” $ haconf_out „po zakończeniu.

Nie tak eleganckie jak pipefail, ale uzasadniona alternatywa, jeśli ta funkcja nie jest w zasięgu.

Odpowiedź

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 $ 

Odpowiedź

(przynajmniej z bash) w połączeniu z set -e można użyć podpowłoki do jawnej emulacji pipefail i wyjścia po błędzie potoku

set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program 

Więc jeśli foo z jakiegoś powodu zawiedzie – reszta programu nie zostanie wykonana, a skrypt zakończy działanie z odpowiednim kodem błędu. (Przy założeniu, że foo wypisuje własny błąd, który jest wystarczający do zrozumienia przyczyny niepowodzenia)

Odpowiedź

EDYTUJ : Ta odpowiedź jest zła, ale interesująca, więc zostawię ją na przyszłość


Dodanie ! 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. # =========================================================== # 

Komentarze

  • Myślę, że to nie ma związku. W Twoim przykładzie chcę poznać kod zakończenia ls, a nie odwracać kodu zakończenia bogus_command
  • Proponuję cofnąć tę odpowiedź.
  • Cóż, wygląda na to, że ' jestem idiotą. ' faktycznie użyłem tego w skrypcie, zanim pomyślałem, że zrobił to, czego chciał OP. Dobrze, że nie ' nie użyłem go do niczego ważnego

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *