Mam dwa procesy foo
i bar
połączone z rurą:
$ foo | bar
bar
zawsze kończy 0; Interesuje mnie kod wyjścia foo
. Czy jest na to jakiś sposób?
Mam dwa procesy foo
i bar
połączone z rurą:
$ foo | bar
bar
zawsze kończy 0; Interesuje mnie kod wyjścia foo
. Czy jest na to jakiś sposób?
Odpowiedzi:
Jeśli używasz bash
, możesz użyć PIPESTATUS
zmiennej 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 bash
lub, zsh
a otrzymasz takie same wyniki; tylko jeden retval_bash
i retval_zsh
zostanie ustawiona. Drugi będzie pusty. Pozwoliłoby to na zakończenie funkcji return $retval_bash $retval_zsh
(zwróć uwagę na brak cudzysłowów!).
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:
Pierwszym sposobem jest ustawienie pipefail
opcji ( ksh
, zsh
lub 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
Bash ma również zmienną tablicową o nazwie $PIPESTATUS
( $pipestatus
in 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.
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
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:
someprog | filter
chcesz 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
wynikiem tego konstruktu jest stdout od filter
jako stdout konstrukcji i status wyjścia od someprog
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.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 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 otwarte deskryptory plików od rodzica. Oznacza to, someprog
że odziedziczy otwarty deskryptor pliku 3 i 4. Jeśli someprog
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łaniem someprog
.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
Znak exec 3>&- 4>&-
przed someprog
zamyka deskryptor pliku przed uruchomieniem, someprog
więc dla someprog
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:
( ( ( ( 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:
#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ściowe filter
są przekierowywane do deskryptora pliku 4. W #part1
deskryptorze pliku 4 został przekierowany na standardowe wyjście. Oznacza to, że wynikiem filter
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 do filter
. Wyjście z filter
will osiąga z kolei standardowe wyjście, jak wyjaśniono w#part4
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
upraszcza someprog 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ć
#!/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
set -o pipefail
wewnątrz skryptu powinna być bardziej niezawodna, np. na wypadek, gdyby ktoś wykonał skrypt za pośrednictwem bash 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 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
Co zrobić, jeśli to możliwe jest, aby karmić kod wyjścia z foo
pod bar
. Na przykład, jeśli wiem, że foo
nigdy 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 foo
nigdy 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 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.
exit_codes=$({ { foo; echo foo:"$?" >&3; } |
{ bar >/dev/null; echo bar:"$?" >&3; }
} 3>&1)
Po tym $exit_codes
zwykle jest foo:X bar:Y
, ale może się zdarzyć, bar:Y foo:X
jeśli bar
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ę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 bar
nigdy 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:
/tmp
jest 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")
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_statuses
z moich testów; bez nich grep
nie 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>&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:
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>&1
odziedziczy 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.
-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ś.
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.
set -o pipefail
in ksh lub dowolną liczbą posypanych wait
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 przy if
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.
(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))
(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
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 | 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:
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:
mktemp
. Zwykle natychmiast tworzy plik/tmp
wait
to potrzebne ksh
, 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
zwraca false
, 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 liczba 0
IN 000
zależ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:
ksh
i podobne powłoki, które tylko czekają na ostatnie polecenie potoku, wymagają wait
odkomentowania
Ostatni 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ż, 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
, sash
a nawetcsh
Przy odrobinie ostrożności powinno to działać:
foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
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 -makerw
i zapisze swoje stdout i stderr na „$ haconf_out”. Jeśli zwrócona wartość haconf
jest prawdą, wówczas blok „if” zostanie wykonany i grep
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.
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
$
(Przynajmniej bash) w połączeniu z set -e
jednym 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 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ę, że foo
drukuje własny błąd, który wystarczy, aby zrozumieć przyczynę niepowodzenia)
EDYCJA : Ta odpowiedź jest błędna, ale interesująca, więc zostawię ją do wykorzystania w przyszłości.
!
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.
# =========================================================== #
ls
, a nie odwracać kod wyjściabogus_command