Uzyskaj status zakończenia procesu, który jest potokowany do innego


Odpowiedzi:


256

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!).


9
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).

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

$ PIPESTATUS

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.

Oddzielne egzekucje

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

2
Cerować! Właśnie pisałem o PIPESTATUSIE.
slm

1
Dla porównania istnieje kilka innych technik omówionych w tym pytaniu SO: stackoverflow.com/questions/1221833/…
slm

1
@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.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

1
Miły. Dla funkcji, którą mógłbym po prostu wykonać(read; exit $REPLY)
jthill

5
(exec 3>&- 4>&-; someprog)upraszcza someprog 3>&- 4>&-.
Roger Pate,

Ta metoda działa również bez podpowłoki:{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
josch

36

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

9
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 -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
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:

{ 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:

  • 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.
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
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:

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.


12

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.


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,


7

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.


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.

(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))

3
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:

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


3

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

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

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:

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.


1
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
$

0

(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)


-1

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

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

1
Myślę, że to nie ma związku. W twoim przykładzie chcę znać kod wyjścia ls, a nie odwracać kod wyjściabogus_command
Michael Mrozek

2
Sugeruję cofnąć tę odpowiedź.
maxschlepzig

3
Wygląda na to, że jestem idiotą. Użyłem tego w skrypcie, zanim pomyślałem, że robi to, czego chciał OP. Dobrze, że nie użyłem tego do niczego ważnego
Falmarri,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.