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!).
I pipestatusw Zsh. Niestety inne pociski nie mają tej funkcji.
Gilles
8
Uwaga: Tablice w zsh zaczynają się odruchowo od indeksu 1, więc tak jest echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm
5
Możesz sprawdzić cały rurociąg w następujący sposób:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0
7
@JanHudec: Być może powinieneś przeczytać pierwsze pięć słów mojej odpowiedzi. Proszę również wskazać, gdzie pytanie wymagało odpowiedzi wyłącznie w standardzie POSIX.
camh
12
@JanHudec: Nie był też oznaczony POSIX. Dlaczego uważasz, że odpowiedzią musi być POSIX? Nie został określony, więc podałem kwalifikowaną odpowiedź. W mojej odpowiedzi nie ma nic niepoprawnego, a ponadto istnieje wiele odpowiedzi na inne przypadki.
camh
237
Można to zrobić na 3 sposoby:
Pipefail
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).
@Patrick rozwiązanie pipestatus działa na bash, tylko więcej pytań w przypadku użycia skryptu ksh, myślisz, że możemy znaleźć coś podobnego do pipestatus? , (od czasu do czasu widzę pipestatus nie obsługiwany przez ksh)
yael 21.04.13
2
@yael Nie używam ksh, ale po krótkim spojrzeniu na stronę podręczną nie obsługuje $PIPESTATUSani nic podobnego. pipefailJednak obsługuje tę opcję.
Patrick,
1
Zdecydowałem się na pipefail, ponieważ pozwala mi uzyskać status nieudanego polecenia tutaj:LOG=$(failed_command | successful_command)
vmrob
51
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.
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
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.
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>&-
Utworzono podpowłokę z deskryptorem pliku 4 przekierowanym na stdout. Oznacza to, że wszystko, co zostanie wydrukowane do deskryptora pliku 4 w podpowłoce, skończy jako standardowe wyjście całej konstrukcji.
Tworzony jest potok i wykonywane są polecenia po lewej ( #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.
Utworzono podpowłokę z deskryptorem pliku 3 przekierowanym na stdout. Oznacza to, że wszystko, co zostanie wydrukowane na deskryptorze pliku 3 w tej podpowłoce, skończy się, #part2a z kolei stanie się kodem wyjścia całej konstrukcji.
Rura jest tworzona i wykonywane są polecenia po lewej ( #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.
Status wyjścia z #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
set -o pipefailwewnątrz skryptu powinna być bardziej niezawodna, np. na wypadek, gdyby ktoś wykonał skrypt za pośrednictwem bash foo.sh.
maxschlepzig
Jak to działa? masz przykład?
Johan
2
Zauważ, że -o pipefailnie ma go w POSIX.
scy
2
To nie działa w mojej wersji BASH 3.2.25 (1). Na górze / tmp / ff mam #!/bin/bash -o pipefail. Błąd to:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez
2
@FelipeAlvarez: W niektórych środowiskach (w tym Linux) nie przetwarza spacji na #!liniach poza pierwszym, a więc staje się to /bin/bash-o pipefail/tmp/ff, zamiast koniecznej /bin/bash-opipefail/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
lindes
22
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:
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:
Jeśli jednocześnie działa wiele skryptów lub jeśli ten sam skrypt używa tej metody w kilku miejscach, musisz upewnić się, że używają różnych nazw plików tymczasowych.
Bezpieczne utworzenie pliku tymczasowego w udostępnionym katalogu jest trudne. Często /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.
Korzystając z metody plików tymczasowych, wolę dodać pułapkę dla polecenia EXIT, która usuwa wszystkie pliki tymczasowe, aby żadne śmieci nie pozostały, nawet jeśli skrypt umrze
cud 173
17
Zaczynając od rurociągu:
foo | bar | baz
Oto ogólne rozwiązanie wykorzystujące tylko powłokę POSIX i brak plików tymczasowych:
$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:
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.
Wygląda interesująco, ale nie do końca wiem, czego oczekujesz od tego polecenia, a mój komputer też nie; I dostać -bash: 3: Bad file descriptor.
G-Man
@ G-Man Racja, ciągle zapominam, że bash nie ma pojęcia, co robi, jeśli chodzi o deskryptory plików, w przeciwieństwie do zwykle używanych przeze mnie powłok (popiołu dostarczanego z busybox). Powiadomię cię, gdy pomyślę o obejściu, które sprawia, że bash jest szczęśliwy. W międzyczasie, jeśli masz pod ręką skrzynkę debianową, możesz wypróbować ją w desce rozdzielczej, lub jeśli masz pod ręką busybox, możesz wypróbować ją z opcją busy / ash.
mtraceur
@ G-Man Jeśli chodzi o to, czego oczekuję od polecenia i co robi w innych powłokach, to przekierowuje standardowe wyjście z polecenia 1, aby nie zostało złapane przez podstawienie polecenia, ale gdy jest poza podstawieniem polecenia, spada. fd3 z powrotem na standardowe wyjście, więc jest przesyłane zgodnie z oczekiwaniami do polecenia2. Kiedy polecenie1 kończy działanie, printf uruchamia się i drukuje swój status wyjścia, który jest przechwytywany do zmiennej przez podstawienie polecenia. Bardzo szczegółowy podział tutaj: stackoverflow.com/questions/985876/tee-and-exit-status/… Również twój komentarz brzmi tak, jakby miał być trochę obraźliwy?
mtraceur
Od czego mam zacząć (1) Przykro mi, jeśli poczułeś się obrażony. „Wygląda interesująco” miał na myśli poważnie; byłoby świetnie, gdyby coś tak kompaktowego działało tak dobrze, jak się tego spodziewałeś. Poza tym mówiłem po prostu, że nie rozumiem, co powinno zrobić twoje rozwiązanie. Pracuję / bawię się z Unixem od dłuższego czasu (odkąd istniał Linux), a jeśli czegoś nie rozumiem, to jest czerwona flaga, która być może inni też tego nie zrozumieją, i że to potrzebuje więcej wyjaśnień (IMNSHO). … (Ciąg dalszy)
G-Man
(Ciąg dalszy) ... Od ciebie „... lubię myśleć, że ... [pan] rozumiem prawie wszystko więcej niż przeciętny człowiek”, może należy pamiętać, że celem Stos Exchange nie ma być usługę polecenia zapisu, ubijaniu spośród tysięcy jednorazowych rozwiązań na trywialnie różne pytania; ale raczej uczyć ludzi, jak rozwiązywać własne problemy. I w tym celu może trzeba wyjaśnić rzeczy wystarczająco dobrze, aby „przeciętny człowiek” mógł to zrozumieć. Spójrz na odpowiedź lesmany jako przykład doskonałego wyjaśnienia. … (Ciąg dalszy)
G-Man,
11
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:
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.
Działa to naprawdę dobrze z większością powłok, z którymi próbowałem, w tym NetBSD sh, pdksh, mksh, dash, bash. Jednak nie mogę zmusić go do pracy z AT&T Ksh (93s +, 93u +) lub zsh (4.3.9, 5.2), nawet z 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ę .
Greg A. Woods,
4
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.
To nie działa z kilku powodów. 1. Plik tymczasowy można odczytać przed jego zapisaniem. 2. Utworzenie pliku tymczasowego we współdzielonym katalogu o przewidywalnej nazwie jest niepewne (trywialne DoS, wyścig symlink). 3. Jeśli ten sam skrypt używa tej sztuczki kilka razy, zawsze użyje 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.
Gilles
+1 Cóż, $ {PIPESTATUS [0]} jest łatwiejszy, ale podstawowy pomysł tutaj działa, jeśli ktoś wie o problemach, o których wspomina Gilles.
Johan
Można zapisać kilka podpowłok: (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ąć.
dubiousjim
4
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:
Masz powłokę, która nie zna $PIPESTATUSaniset -o pipefail
Chcesz użyć potoku do równoległego wykonywania, więc nie ma plików tymczasowych.
Nie chcesz mieć dodatkowego bałaganu, jeśli przerwiesz skrypt, być może z powodu nagłej przerwy w zasilaniu.
To rozwiązanie powinno być stosunkowo łatwe do naśladowania i czytelne.
Nie chcesz wprowadzać dodatkowych podpowłok.
Nie możesz majstrować przy istniejących deskryptorach plików, więc nie można dotykać stdin / out / err (możesz jednak tymczasowo wprowadzić nowe)
Dodatkowe 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:
Plik tymczasowy jest tworzony za pomocą mktemp. Zwykle natychmiast tworzy plik/tmp
Ten plik tymczasowy jest następnie przekierowywany do FD 9 w celu zapisu i FD 8 w celu odczytu
Następnie plik tymczasowy jest natychmiast usuwany. Pozostaje jednak otwarty, dopóki obie FD nie znikną.
Teraz rura jest uruchomiona. Każdy krok dodaje się tylko do FD 9, jeśli wystąpił błąd.
Jest waitto 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ć.
Następnie zawartość pliku jest odczytywana. Jeśli jest pusty (ponieważ wszystko działało) readzwraca false, trueoznacza to błąd
Może to być użyte jako zamiennik wtyczki dla pojedynczego polecenia i wymaga tylko:
Niewykorzystane FD 9 i 8
Pojedyncza zmienna środowiskowa do przechowywania nazwy pliku tymczasowego
Ten przepis można dostosować do dowolnej powłoki, która umożliwia przekierowanie IO
Jest także dość niezależny od platformy i nie potrzebuje takich rzeczy /proc/fd/N
Robaki:
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:
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
Co powiesz na sprzątanie jak jlliagre? Czy nie pozostawiasz pliku o nazwie foo-status?
Johan
@Johan: Jeśli wolisz moją sugestię, nie wahaj się zagłosować;) Oprócz tego, że nie pozostawia pliku, ma tę zaletę, że umożliwia jednoczesne uruchomienie wielu procesów, a bieżący katalog nie musi być zapisywalny.
jlliagre
2
Następujący blok „if” będzie działał tylko wtedy, gdy polecenie „komenda” się powiedzie:
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.
(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)
# =========================================================== ## 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.# =========================================================== #
Odpowiedzi:
Jeśli używasz
bash
, możesz użyćPIPESTATUS
zmiennej tablicowej, aby uzyskać status wyjścia każdego elementu potoku.Jeśli używasz
zsh
, tablica jest nazywanapipestatus
(wielkość liter ma znaczenie!), A indeksy tablic zaczynają się od jednego:Aby połączyć je w ramach funkcji w sposób, który nie powoduje utraty wartości:
Uruchom powyższe w
bash
lub,zsh
a otrzymasz takie same wyniki; tylko jedenretval_bash
iretval_zsh
zostanie ustawiona. Drugi będzie pusty. Pozwoliłoby to na zakończenie funkcjireturn $retval_bash $retval_zsh
(zwróć uwagę na brak cudzysłowów!).źródło
pipestatus
w 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:
Pipefail
Pierwszym sposobem jest ustawienie
pipefail
opcji (ksh
,zsh
lubbash
). 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).$ PIPESTATUS
Bash ma również zmienną tablicową o nazwie
$PIPESTATUS
($pipestatus
inzsh
), która zawiera status wyjścia wszystkich programów w ostatnim potoku.Możesz użyć trzeciego przykładu polecenia, aby uzyskać określoną wartość w potoku, której potrzebujesz.
Oddzielne egzekucje
To najbardziej niewygodne z rozwiązań. Uruchom każdą komendę osobno i zapisz status
źródło
ksh
, ale po krótkim spojrzeniu na stronę podręczną nie obsługuje$PIPESTATUS
ani nic podobnego.pipefail
Jednak 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:
chcesz status wyjścia z
someprog
i wyjście zfilter
.Oto moje rozwiązanie:
wynikiem tego konstruktu jest stdout od
filter
jako stdout konstrukcji i status wyjścia odsomeprog
jako 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.htmlNiestety 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
someprog
ifilter
:Przykładowe dane wyjściowe:
Uwaga: proces potomny dziedziczy otwarte deskryptory plików od rodzica. Oznacza to,
someprog
że odziedziczy otwarty deskryptor pliku 3 i 4. Jeślisomeprog
zapisze w deskryptorze pliku 3, stanie się to kodem wyjścia. Rzeczywisty status wyjścia zostanie zignorowany, ponieważread
czyta tylko raz.Jeśli obawiasz się, że możesz
someprog
napisać do deskryptora pliku 3 lub 4, najlepiej zamknąć deskryptory plików przed wywołaniemsomeprog
.Znak
exec 3>&- 4>&-
przedsomeprog
zamyka deskryptor pliku przed uruchomieniem,someprog
więc dlasomeprog
tych 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:
Od dołu do góry:
#part3
) i prawej (#part2
).exit $xs
jest także ostatnim poleceniem potoku, co oznacza, że ciąg ze standardowego wejścia będzie statusem wyjścia całej konstrukcji.#part2
a z kolei stanie się kodem wyjścia całej konstrukcji.#part5
i#part6
) i po prawej (filter >&4
). Dane wyjściowefilter
są przekierowywane do deskryptora pliku 4. W#part1
deskryptorze pliku 4 został przekierowany na standardowe wyjście. Oznacza to, że wynikiemfilter
jest stdout całego konstruktu.#part6
jest drukowany do deskryptora pliku 3. W#part3
deskryptorze pliku 3 został przekierowany do#part2
. Oznacza to, że status wyjścia z#part6
będzie końcowym statusem wyjścia dla całej konstrukcji.someprog
jest wykonywany. Status wyjścia jest brany pod uwagę#part5
. Stdout jest pobierany przez rurkę#part4
i przekazywany dofilter
. Wyjście zfilter
will osiąga z kolei standardowe wyjście, jak wyjaśniono w#part4
źródło
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
upraszczasomeprog 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ć
dzięki czemu Twoje rury zwracają ostatni niezerowy zwrot.
może być nieco mniej kodowania
Edycja: przykład
źródło
set -o pipefail
wewnątrz skryptu powinna być bardziej niezawodna, np. na wypadek, gdyby ktoś wykonał skrypt za pośrednictwembash foo.sh
.-o pipefail
nie 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 wARGV
, 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łoexec /bin/bash -o pipefail "$@"
, i umieść to w#!
wierszu, to by działało. Zobacz także: en.wikipedia.org/wiki/Shebang_%28Unix%29Co zrobić, jeśli to możliwe jest, aby karmić kod wyjścia z
foo
podbar
. Na przykład, jeśli wiem, żefoo
nigdy nie tworzy wiersza zawierającego tylko cyfry, mogę po prostu podać kod wyjścia:Lub jeśli wiem, że dane wyjściowe
foo
nigdy nie zawierają wiersza zawierającego tylko.
:Można to zawsze zrobić, jeśli istnieje jakiś sposób na rozpoczęcie
bar
pracy na wszystkich wierszach oprócz ostatniego i przekazanie ostatniego wiersza jako kodu wyjścia.W
bar
przypadku 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.Po tym
$exit_codes
zwykle jestfoo:X bar:Y
, ale może się zdarzyć,bar:Y foo:X
jeślibar
zakoń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ęcfoo:$?
ibar:$?
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ściebar
nigdy nie zawierało wiersza, który wygląda jak kod wyjścia, ale robi się niezręcznie.I oczywiście istnieje prosta opcja użycia pliku tymczasowego do przechowywania statusu. Proste, ale nie takie proste w produkcji:
/tmp
jest to jedyne miejsce, w którym skrypt z pewnością będzie w stanie zapisywać pliki. Użyjmktemp
, który nie jest POSIX, ale jest obecnie dostępny na wszystkich poważnych jednorożcach.źródło
Zaczynając od rurociągu:
Oto ogólne rozwiązanie wykorzystujące tylko powłokę POSIX i brak plików tymczasowych:
$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.Zwróć uwagę na cytaty
$error_statuses
z moich testów; bez nichgrep
nie można odróżnić, ponieważ nowe linie są wymuszane na spacje.źródło
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:
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>&1
zrobiliś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>&1
Musi 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:
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>&1
odziedziczy deskryptor pliku trzy. Dlatego4>&-
upewnia się, że wewnętrzna komenda złożona nie odziedziczy deskryptora pliku cztery, i3>&-
nie odziedziczy deskryptora pliku trzy, więc polecenie1 otrzymuje „czystsze”, bardziej standardowe środowisko. Możesz także przesunąć wnętrze4>&-
obok3>&-
, 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.
źródło
-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ś.
źródło
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: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.źródło
set -o pipefail
in ksh lub dowolną liczbą posypanychwait
poleceń 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 przyif
wyborze 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.
Edycja: tutaj jest silniejsza wersja po komentarzach Gillesa:
Edycja2: i tutaj jest nieco lżejszy wariant po komentarzu dubiousjim:
źródło
(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:
$PIPESTATUS
aniset -o pipefail
Przed:
foo | bar | baz
jednak 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:
Wyjaśniono:
mktemp
. Zwykle natychmiast tworzy plik/tmp
wait
to potrzebneksh
, ponieważksh
inaczej 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ć.read
zwracafalse
,true
oznacza to błądMoże to być użyte jako zamiennik wtyczki dla pojedynczego polecenia i wymaga tylko:
/proc/fd/N
Robaki:
W tym skrypcie występuje błąd w przypadku
/tmp
braku 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 liczba0
IN000
zależy od liczby poleceń w rurze, więc jest to nieco bardziej skomplikowane:Uwagi dotyczące przenośności:
ksh
i podobne powłoki, które tylko czekają na ostatnie polecenie potoku, wymagająwait
odkomentowaniaOstatni przykład używa
printf "%1s" "$?"
zamiast,echo -n "$?"
ponieważ jest to bardziej przenośne. Nie każda platforma interpretuje-n
poprawnie.printf "$?"
zrobiłby to również, aleprintf "%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
,sash
a nawetcsh
źródło
Przy odrobinie ostrożności powinno to działać:
źródło
Następujący blok „if” będzie działał tylko wtedy, gdy polecenie „komenda” się powiedzie:
Mówiąc konkretnie, możesz uruchomić coś takiego:
Który uruchomi się
haconf -makerw
i zapisze swoje stdout i stderr na „$ haconf_out”. Jeśli zwrócona wartośćhaconf
jest prawdą, wówczas blok „if” zostanie wykonany igrep
bę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.źródło
źródło
(Przynajmniej bash) w połączeniu z
set -e
jednym można użyć podpowłoki, aby jawnie emulować pipefail i wyjść z błędu potokuJeśli więc
foo
z 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ę, żefoo
drukuje własny błąd, który wystarczy, aby zrozumieć przyczynę niepowodzenia)źródło
EDYCJA : Ta odpowiedź jest błędna, ale interesująca, więc zostawię ją do wykorzystania w przyszłości.
Dodanie a
!
do polecenia powoduje odwrócenie kodu powrotu.http://tldp.org/LDP/abs/html/exit-status.html
źródło
ls
, a nie odwracać kod wyjściabogus_command