Mám dva procesy foo
a bar
spojené s potrubím:
$ foo | bar
bar
vždy opustí 0; Zajímá mě výstupní kód foo
. Existuje nějaký způsob, jak se k tomu dostat?
Komentáře
- stackoverflow.com/questions/1221833/…
Odpověď
bash
a zsh
mají proměnnou pole který obsahuje stav ukončení každého prvku (příkazu) posledního kanálu provedeného shellem.
Pokud používáte bash
, pole se nazývá PIPESTATUS
(na případech záleží!) a indexy polí začínají nulou:
$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0
Pokud používáte zsh
, pole se nazývá pipestatus
(na případech záleží!) a indexy pole začínají na jednom:
$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0
Chcete-li je v rámci funkce kombinovat způsobem, který neztratí hodnoty:
$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0
Spusťte výše uvedené v bash
nebo zsh
a získáte stejné výsledky; nastaven bude pouze jeden z retval_bash
a retval_zsh
. Druhý bude prázdný. To by umožnilo funkci končit return $retval_bash $retval_zsh
(všimněte si nedostatku uvozovek!).
Komentáře
- A
pipestatus
v zsh. Jiné funkce bohužel tuto funkci nemají ‚ - Poznámka: Pole v zsh začínají neintuitivně na indexu 1, takže ‚ s
echo "$pipestatus[1]" "$pipestatus[2]"
. - Celý kanál můžete zkontrolovat takto:
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
- @JanHudec: Možná byste si měli přečíst prvních pět slov mé odpovědi. Také laskavě upozorněte, kde otázka vyžadovala odpověď pouze pro POSIX.
- @JanHudec: Ani nebyl označen jako POSIX. Proč si myslíte, že odpověď musí být POSIX? Nebylo to specifikováno, tak jsem poskytl kvalifikovanou odpověď. Na mé odpovědi není nic nesprávného a navíc existuje několik odpovědí na řešení dalších případů.
Odpověď
Tady jsou 3 běžné způsoby, jak toho dosáhnout:
Pipefail
Prvním způsobem je nastavení možnosti pipefail
(ksh
, zsh
nebo bash
). Toto je nejjednodušší a to, co dělá, je v zásadě nastavit stav ukončení $?
na kód ukončení posledního programu, aby byl ukončen nenulový (nebo nula, pokud byly všechny úspěšně ukončeny).
$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1
$ PIPESTATUS
Bash má také proměnnou pole s názvem $PIPESTATUS
($pipestatus
v zsh
), který obsahuje stav ukončení všech programů v posledním kanálu.
$ true | true; echo "${PIPESTATUS[@]}" 0 0 $ false | true; echo "${PIPESTATUS[@]}" 1 0 $ false | true; echo "${PIPESTATUS[0]}" 1 $ true | false; echo "${PIPESTATUS[@]}" 0 1
Třetí příklad příkazu můžete použít k získání konkrétní hodnoty v kanálu, kterou potřebujete.
Oddělené spouštění
Toto je nejnepříznivější řešení. Spusťte každý příkaz samostatně a zachyťte stav
$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1
Komentáře
- Sakra! Právě jsem chtěl zveřejnit příspěvek o PIPESTATUS.
- Pro referenci je v této SO otázce diskutováno několik dalších technik: stackoverflow.com/questions/1221833/…
- @Patrick řešení pipestatus funguje na bash, jen větší kvadrant v případě, že použiji skript ksh, myslíte si, že můžeme najít něco podobného pipestatus? , (mezitím vidím, že pipestatus není podporován ksh)
- @yael Nepoužívám ‚
ksh
, ale z krátkého pohledu na ‚ s manpage nepodporuje ‚$PIPESTATUS
nebo něco podobného. Podporuje však možnostpipefail
. - Rozhodl jsem se jít s pipefailem, protože mi zde umožňuje zjistit stav neúspěšného příkazu:
LOG=$(failed_command | successful_command)
odpověď
Toto řešení funguje bez použití specifických funkcí bash nebo dočasných souborů . Bonus: stav ukončení je ve skutečnosti stav ukončení a ne nějaký řetězec v souboru.
Situace:
someprog | filter
vy chcete stav ukončení z someprog
a výstup z filter
.
Zde je moje řešení:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
výsledkem tohoto konstruktu je standardní výstup z filter
jako standardní výstup z konstruktu a ukončení z someprog
jako stav ukončení konstrukce.
tento konstrukt funguje také s jednoduchým seskupením příkazů {...}
namísto subshells (...)
. dílčí skořápky mají určité důsledky, mimo jiné náklady na výkon, které zde nepotřebujeme. v podrobném manuálu bash najdete další podrobnosti: 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
Bohužel bash gramatika vyžaduje mezery a středníky pro složené závorky, aby se konstrukce stala mnohem prostornější.
Ve zbývající části tohoto textu použiji variantu subshell.
Příklad someprog
a 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 $?
Příklad výstupu:
filtered line1 filtered line2 filtered line3 42
Poznámka: podřízený proces zdědí otevřené nadpisy souborů od rodiče. To znamená, že someprog
zdědí otevřený deskriptor souborů 3 a 4. Pokud someprog
zapíše do deskriptoru souboru 3, stane se tímto stavem ukončení. Skutečný stav ukončení bude ignorován, protože read
čte pouze jednou.
Pokud se obáváte, že váš someprog
může psát k deskriptoru souborů 3 nebo 4, je nejlepší zavřít deskriptory souborů před voláním someprog
.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
exec 3>&- 4>&-
před someprog
zavře deskriptor souboru před provedením someprog
tak pro someprog
tyto deskriptory souborů jednoduše neexistují.
Může být také napsán takto: someprog 3>&- 4>&-
Krok za krokem vysvětlení konstrukce:
( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1
Zdola nahoru:
- Je vytvořen subshell s deskriptorem souboru 4 přesměrován na standardní výstup. To znamená, že vše, co se vytiskne do deskriptoru souboru 4 v subshell, skončí jako výstup celého konstruktu.
- Bude vytvořena roura a příkazy vlevo (
#part3
) a vpravo (#part2
) jsou provedeny.exit $xs
je také posledním příkazem kanálu, což znamená, že řetězec ze stdin bude stavem ukončení celé konstrukce. - Se souborem se vytvoří subshell. deskriptor 3 přesměrován na standardní výstup. To znamená, že vše, co je vytištěno do deskriptoru souboru 3 v tomto subshellu, skončí v
#part2
a bude to stav ukončení celého konstruktu. - A potrubí je vytvořeno a příkazy vlevo (
#part5
a#part6
) a vpravo (filter >&4
) jsou provedeny. Výstupfilter
je přesměrován na deskriptor souboru 4. V#part1
byl deskriptor souboru 4 přesměrován na standardní výstup. To znamená, že výstupfilter
je standardním výstupem celého konstruktu. - Stav ukončení z
#part6
je vytištěn do deskriptoru souboru 3. V#part3
byl deskriptor souboru 3 přesměrován do#part2
. To znamená, že stav ukončení z#part6
bude konečným stavem ukončení pro celou konstrukci. -
someprog
je popraven. Stav ukončení je převzat z#part5
. Standardní výstup je převzat potrubím v#part4
a předán dofilter
. Výstup zfilter
zase dosáhne standardního výstupu, jak je vysvětleno v#part4
komentářích
- Hezké. Pro tuto funkci bych mohl
(read; exit $REPLY)
-
(exec 3>&- 4>&-; someprog)
zjednodušit nasomeprog 3>&- 4>&-
. - Tato metoda funguje také bez dílčích skořápek:
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Odpovědět
Ačkoli to není přesně to, co jste požadovali, můžete použít
#!/bin/bash -o pipefail
, aby vaše potrubí vrátila poslední nenulový návrat.
Může to být trochu méně kódování
Upravit: Příklad
[root@localhost ~]# false | true [root@localhost ~]# echo $? 0 [root@localhost ~]# set -o pipefail [root@localhost ~]# false | true [root@localhost ~]# echo $? 1
Komentáře
-
set -o pipefail
uvnitř skriptu by měl být robustnější, např. v případě, že někdo provede skript pomocíbash foo.sh
. - Jak to funguje? máte příklad?
- Všimněte si, že
-o pipefail
není v POSIXu. - V mém BASH 3.2.25 to nefunguje ( 1) – vydání. V horní části / tmp / ff mám
#!/bin/bash -o pipefail
.Chyba je:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
- @FelipeAlvarez: Některá prostředí (včetně linuxu) nedokáží ‚ analyzovat mezery v
#!
řádky nad rámec prvního, takže se z toho stane/bin/bash
-o pipefail
/tmp/ff
místo potřebných/bin/bash
-o
pipefail
/tmp/ff
–getopt
(nebo podobné) zpracování pomocíoptarg
, což je další položka vARGV
, jako argument-o
, takže selže. Pokud byste měli vytvořit obálku (řekněmebash-pf
, která právě udělalaexec /bin/bash -o pipefail "$@"
a umístit ji na#!
řádek, který by fungoval. Viz také: en.wikipedia.org/wiki/Shebang_%28Unix%29
Odpověď
Co dělám, když je to možné, je vložení výstupního kódu z foo
do bar
. Například pokud vím, že foo
nikdy nevytvoří řádek pouze s číslicemi, mohu jen kliknout na kód ukončení:
{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}"
Nebo pokud vím, že výstup z foo
nikdy neobsahuje řádek pouze s .
:
{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}"
To lze provést vždy, pokud existuje způsob, jak získat bar
pracovat na všech řádcích kromě posledního a předat poslední řádek jako výstupní kód.
Pokud bar
je složitý kanál, jehož výstupem je nepotřebujete, část můžete obejít vytištěním výstupního kódu na jiný deskriptor souboru.
exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1)
Poté $exit_codes
je obvykle foo:X bar:Y
, ale mohlo by to být bar:Y foo:X
pokud bar
skončí před přečtením všech jeho vstupů nebo pokud máte smůlu. Myslím, že zápisy do kanálů do 512 bajtů jsou atomické ve všech unices, takže foo:$?
a bar:$?
části nebudou vzájemně smíchány, protože pokud jsou řetězce značek menší než 507 bajtů.
Pokud potřebujete zachytit výstup z bar
, bude to obtížné. Výše uvedené techniky můžete kombinovat uspořádáním aby výstup bar
nikdy neobsahoval řádek, který by vypadal jako indikace výstupního kódu, ale byl by fiddly.
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")
A samozřejmě existuje jednoduchá možnost použít dočasný soubor k uložení stavu. Jednoduché, ale ne to jednoduché v produkci:
- Pokud je spuštěno více skriptů současně, nebo pokud stejný skript používá tuto metodu na několika místech, musíte provést ujistěte se, že používají různé dočasné názvy souborů.
- Vytvoření bezpečného dočasného souboru ve sdíleném adresáři je obtížné.
/tmp
je často jediným místem, kde skript určitě dokáže zapisovat soubory. Použijtemktemp
, který není POSIX, ale je dnes k dispozici ve všech vážných unices.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
Komentáře
- Při použití přístupu dočasných souborů dávám přednost přidání pasti pro EXIT, která odstraní všechny dočasné soubory takže nezůstanou žádné odpadky, i když skript umře
Odpovědět
Počínaje od kanálu:
foo | bar | baz
Zde je obecné řešení využívající pouze POSIX shell a žádné dočasné soubory:
exec 4>&1 error_statuses="`((foo || echo "0:$?" >&3) | (bar || echo "1:$?" >&3) | (baz || echo "2:$?" >&3)) 3>&1 >&4`" exec 4>&-
$error_statuses
obsahuje stavové kódy všech neúspěšných procesů v náhodném pořadí s indexy, které určují, který příkaz vydal každý stav.
# 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
V mých testech si všimněte uvozovek kolem $error_statuses
; bez nich grep
nelze rozlišit, protože nové řádky jsou vynuceny do mezer.
Odpovědět
Pokud máte nainstalovaný balíček moreutils , můžete použít nástroj mispipe , který dělá přesně to, co jste požadovali.
Odpověď
Chtěl jsem tedy přispět odpovědí jako lesmana, ale myslím, že moje je možná trochu jednodušší a o něco výhodnější čistá – Řešení Bourne-shell:
# 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.
Myslím, že je to nejlépe vysvětlit zevnitř ven – command1 provede a vytiskne svůj běžný výstup na stdout (deskriptor souboru 1) , poté, co je hotovo, printf provede a vytiskne výstupní kód command1 na jeho stdout, ale tento stdout je přesměrován na deskriptor souboru 3.
Je-li spuštěn command1, jeho stdout je směrován na command2 (printfův výstup se nikdy nedostane na command2, protože ho posíláme do deskriptoru souboru 3 místo 1, což co čte roura).Potom přesměrujeme výstup příkazu 2 na deskriptor souboru 4, aby také zůstal mimo deskriptor souboru 1 – protože chceme, aby byl deskriptor souboru 1 zdarma o něco později, protože přivedeme výstup printf na deskriptor souboru 3 zpět dolů do deskriptor souboru 1 – protože to je to, co nahradí příkaz (backticks), a to je to, co se dostane do proměnné.
Poslední bit kouzla je ten první exec 4>&1
provedli jsme jako samostatný příkaz – otevře deskriptor souboru 4 jako kopii standardního výstupu externího prostředí. Substituce příkazů zachytí vše, co je napsáno na standardu, z pohledu příkazů v něm – ale protože výstup příkazu2 jde do deskriptoru souboru 4, pokud jde o substituci příkazů, substituce příkazu jej nezachytí – jakmile se však „dostane“ z nahrazení příkazu, bude se efektivně stále věnovat celkovému deskriptoru souboru 1. Skript
(exec 4>&1
má být samostatným příkazem, protože mnoho běžných skořápek se mu nelíbí, když se pokoušíte zapsat do deskriptoru souboru uvnitř substituce příkazu, který je otevřen v příkazu „externí“, který používá substituci. Toto je tedy nejjednodušší přenosný způsob můžete to udělat.)
Můžete se na to dívat méně technickým a hravějším způsobem, jako by se výstupy příkazů navzájem přeskakovaly: příkaz1 přivede potrubí na příkaz2, pak výstup printf přeskočí nad příkazem 2, aby jej příkaz2 nezachytil a potom výstup příkazu 2 přeskakoval a odcházel Substituce příkazů stejně jako printf přistane právě včas, aby byla substitucí zachycena, aby skončila v proměnné, a výstup příkazu2 pokračuje veselým způsobem a je zapsán na standardní výstup, stejně jako v normálním potrubí.
Jak chápu, $?
bude stále obsahovat návratový kód druhého příkazu v potrubí, protože přiřazení proměnných, substituce příkazů a složené příkazy jsou vše účinně transparentní pro návratový kód příkazu v nich, takže návratový stav příkazu2 by se měl šířit ven – toto a bez nutnosti definovat další funkci je důvod, proč si myslím, že by to mohlo být poněkud lepší řešení než navrhované podle lesmana.
Podle výhrad, které Lesmana zmiňuje, je možné, že příkaz1 v určitém okamžiku skončí pomocí deskriptorů souborů 3 nebo 4, abyste byli robustnější:
exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&-
Všimněte si, že v mém příkladu používám složené příkazy, ale podsloupce (pomocí ( )
místo { }
bude také fungovat, i když možná bude méně efektivní.)
Příkazy zdědí deskriptory souborů z procesu který je spustí, takže celý druhý řádek zdědí deskriptor souboru čtyři a složený příkaz následovaný 3>&1
zdědí deskriptor souboru tři. Takže 4>&-
zajišťuje, že vnitřní složený příkaz nezdědí deskriptor souboru čtyři, a 3>&-
nezdědí deskriptor souboru tři, takže command1 získá „čistší“ a standardnější prostředí. Můžete také přesunout vnitřní 4>&-
vedle 3>&-
, ale myslím, že proč neomezit co nejvíce jeho rozsah.
Nejsem si jistý, jak často věci používají přímo deskriptor souboru tři a čtyři – myslím, že většina časových programů používá syscall, které vracejí deskriptory souborů, které se nepoužívají, ale někdy kód zapíše do souboru deskriptor 3 přímo, myslím (dovedl bych si představit, že program kontroluje deskriptor souboru, aby zjistil, zda je otevřený, a jeho použití, pokud je, nebo choval se jinak, pokud tomu tak není). Takže ten druhý je pravděpodobně nejlepší zachovat na mysli a použití pro obecné účely.
Komentáře
- Vypadá to zajímavě, ale mohu ‚ T celkem nezjistím, co od tohoto příkazu očekáváte, a můj počítač ‚ t buď; dostanu
-bash: 3: Bad file descriptor
. - @ G-Man Správně, stále zapomínám, že bash nemá tušení, co ‚ dělá, když spolupracuje mes na deskriptory souborů, na rozdíl od skořápek, které obvykle používám (popel dodávaný s busyboxem). ‚ dám vám vědět, když si pomyslím na řešení, které dělá bash šťastným. Pokud jste mezitím ‚ dostali po ruce debian box, můžete to zkusit v pomlčce, nebo pokud ‚ máte busybox handy you může to zkusit s busybox ash / sh.
- @ G-Man Pokud jde o to, co očekávám od příkazu, a co dělá v jiných skořápkách, je přesměrování stdout z příkazu1, takže to není ‚ nedojde k chycení při nahrazování příkazů, ale jakmile bude mimo nahrazení příkazu, dojde k poklesu fd3 zpět na standardní výstup, takže ‚ se posílá podle očekávání na příkaz2.Po ukončení příkazu1 printf vypálí a vytiskne jeho stav ukončení, který je do proměnné zachycen substitucí příkazu. Velmi podrobné rozdělení zde: stackoverflow.com/questions/985876/tee-and-exit-status/… také , ten váš komentář zněl, jako by to mělo být trochu urážlivé?
- Kde mám začít? (1) Je mi líto, pokud jste se cítili uraženi. „Vypadá zajímavě“ bylo míněno vážně; bylo by skvělé, kdyby něco tak kompaktního fungovalo tak dobře, jak jste čekali. Kromě toho jsem jednoduše říkal, že jsem nepochopil, co má vaše řešení dělat. Pracuji / hraji s Unixem dlouhou dobu (od doby, kdy existoval Linux), a pokud něčemu nerozumím, je to červená vlajka, která možná, ostatní to také nepochopí a že to potřebuje další vysvětlení (IMNSHO). … (Pokračování)
- (pokračování) … od vy „… rádi si myslíte … že [rozumíte] téměř vše více než průměrný člověk “, možná byste si měli pamatovat, že cílem Stack Exchange není být službou psaní příkazů, která chrlí tisíce jednorázových řešení triviálně odlišných otázek; ale spíše naučit lidi, jak řešit své vlastní problémy. A za tímto účelem možná budete muset dostatečně dobře vysvětlit věci, aby tomu rozuměl „průměrný člověk“. Podívejte se na odpověď Lesmany jako příklad vynikajícího vysvětlení. … (pokračování)
Odpověď
výše uvedené řešení Lesmana lze také provést bez režie spouštění vnořených podprocesů pomocí { .. }
(nezapomeňte, že tato forma seskupených příkazů musí vždy končit středníky). Něco jako toto:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Zkontroloval jsem tento konstrukt pomocí dash verze 0.5.5 a bash verze 3.2.25 a 4.2.42, takže i když některé granáty nepodporují { .. }
seskupení, stále je to kompatibilní s POSIX.
Komentáře
- Toto funguje opravdu dobře s většinou prostředí I ‚ jsem to zkusil s, včetně NetBSD sh, pdksh, mksh, dash, bash. Nemohu to ale ‚ přivést k práci s AT & T Ksh (93s +, 93u +) nebo zsh (4.3.9, 5.2), dokonce s
set -o pipefail
v ksh nebo libovolném počtu sypanýchwait
v obou. Myslím, že to částečně může přinejmenším buďte otázkou analýzy pro ksh, jako kdybych se držel používání subshells, pak to funguje dobře, ale i přiif
zvolit variantu subshell pro ksh, ale nechat složené příkazy pro ostatní selže.
Odpověď
Následující text je míněn jako doplněk k odpovědi @Patrika, v případě, že nejste schopni použít jedno z běžných řešení.
Tato odpověď předpokládá následující:
- Máte prostředí, které neví o
$PIPESTATUS
aniset -o pipefail
- Chcete použít kanál pro paralelní spuštění, takže žádné dočasné soubory.
- Vy nechcete mít po ruce další nepořádek, pokud skript přerušíte, pravděpodobně v důsledku náhlého výpadku napájení.
- Toto řešení by mělo být relativně snadno sledovatelné a čitelné.
- Nechci zavádět další podsloupce.
- Nemůžete hrát s existujícími deskriptory souborů, takže se nesmíte dotknout stdin / out / err (jak kdykoli můžete dočasně představit nový)
Další předpoklady. Můžete se zbavit všeho, ale tento recept příliš zahlcuje, takže zde není uveden:
- Vše, co chcete vědět, je, že všechny příkazy v PIPE mají výstupní kód 0.
- Nepotřebujete další informace o postranním pásmu.
- Váš shell čeká na návrat všech příkazů kanálu.
Před: foo | bar | baz
, vrátí se však pouze kód ukončení posledního příkazu (baz
)
Hledáno: $?
nesmí být 0
(true), pokud některý z příkazů v kanálu selhal
After:
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
Vysvětlení:
- Tempfile je vytvořen pomocí
mktemp
. Toto obvykle okamžitě vytvoří soubor v/tmp
- Tento tempfile je poté přesměrován na FD 9 pro zápis a FD 8 pro čtení
- Pak tempfile je okamžitě smazán. Zůstává však otevřený, dokud oba FD nevymizí.
- Nyní je kanál spuštěn. Každý krok se přidá pouze k FD 9, pokud došlo k chybě.
-
wait
je potřeba proksh
, protožeksh
else nečeká na dokončení všech příkazů kanálu. Mějte však na paměti, že pokud existují nějaké úlohy na pozadí, existují nežádoucí vedlejší účinky, takže jsem to ve výchozím nastavení komentoval.Pokud to čekání nebolí, můžete to okomentovat. - Poté se načte obsah souboru. Pokud je prázdný (protože vše fungovalo)
read
vracífalse
, takžetrue
označuje chybu
To lze použít jako náhradu pluginu pro jediný příkaz a potřebuje pouze následující:
- Nepoužité FD 9 a 8
- Jedna proměnná prostředí, která obsahuje název tempfile
- A toto recept lze přizpůsobit docela libovolnému prostředí, které umožňuje přesměrování IO
- Také je poměrně agnostický na platformě a nepotřebuje věci jako
/proc/fd/N
CHYBY:
Tento skript obsahuje chybu pro případ, že by /tmp
došlo místo. Pokud také potřebujete ochranu před tímto umělým případem, můžete to udělat následovně, ale má to tu nevýhodu, že počet 0
v 000
závisí na počtu příkazů vpotrubí, takže je to o něco složitější:
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"
Poznámky k přenositelnosti:
-
ksh
a podobné skořápky, které čekají pouze na poslední příkaz kanálu, vyžadujíwait
nekomentované -
Poslední příklad používá
printf "%1s" "$?"
místoecho -n "$?"
, protože je přenosnější. Ne každá platforma interpretuje-n
správně. -
printf "$?"
by to také udělala,printf "%1s"
však zachytí některé rohové případy pro případ, že byste skript spustili na opravdu nefunkční platformě. (Přečtěte si: pokud náhodou programujete vparanoia_mode=extreme
.) -
FD 8 a FD 9 mohou být vyšší na platformách, které podporují více číslice. AFAIR POSIX shodný shell potřebuje pouze podporu jednotlivých číslic.
-
Byl testován s Debianem 8.2
sh
,bash
,ksh
,ash
,sash
a dokonce icsh
Odpověď
Toto je přenosné, tj. pracuje s jakýmkoli shellem kompatibilním s POSIX, nevyžaduje, aby byl aktuální adresář zapisovatelný, a umožňuje současné spuštění více skriptů pomocí stejného triku.
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
Upravit: po Gillesových komentářích je silnější verze:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: a zde je o něco světlejší varianta po komentáři dubiousjim:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
Komentáře
- To ‚ nefunguje z několika důvodů. 1. Dočasný soubor je možné přečíst před jeho ‚ zpracováním. 2. Vytvoření dočasného souboru ve sdíleném adresáři s předvídatelným názvem je nezabezpečené (triviální DoS, závod se symbolickými odkazy). 3. Pokud stejný skript používá tento trik několikrát, ‚ vždy použije stejný název souboru. Chcete-li vyřešit 1, přečtěte si soubor po dokončení kanálu. Chcete-li vyřešit 2 a 3, použijte dočasný soubor s náhodně generovaným jménem nebo v soukromém adresáři.
- +1 No $ {PIPESTATUS [0]} je jednodušší, ale základní myšlenka zde funguje, pokud jeden ví o problémech, které Gilles zmiňuje.
- Můžete si uložit několik podškrupin:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Souhlasím ‚ s Bashem jednodušší, ale v některých kontextech vědět, jak se Bashovi vyhnout, stojí za to.
Odpověď
S trochou opatrnosti by to mělo fungovat:
foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status)
Komentáře
- Co takhle vyčistit jako jlliagre? Nenechávejte ‚ soubor, který se jmenuje foo-status?
- @Johan: Pokud dáváte přednost mému návrhu, don ‚ neváhejte hlasovat;) Kromě toho, že soubor neopustí, má tu výhodu, že umožňuje spuštění více procesů současně a do aktuálního adresáře nemusí být možné zapisovat.
Odpověď
Následující blok „if“ bude spuštěn, pouze pokud bude „příkaz“ úspěšný:
if command; then # ... fi
Konkrétně můžete spustit něco takového:
haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi
Který poběží haconf -makerw
a uložte jeho stdout a stderr do „$ haconf_out“. Pokud je vrácená hodnota z haconf
pravdivá, bude blok „if“ proveden a grep
přečte „$ haconf_out“ a pokusí se porovnat jej s „Cluster již zapisovatelný“.
Všimněte si, že potrubí se automaticky čistí; s přesměrováním si budete muset dát pozor, abyste po dokončení odstranili „$ haconf_out“.
Ne tak elegantní jako pipefail
, ale legitimní alternativa, pokud je tato funkce není na dosah.
Odpověď
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 $
Odpověď
(alespoň s bash) v kombinaci s set -e
lze pomocí subshellu explicitně emulovat pipefail a ukončit chybu kanálu
set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program
Takže pokud foo
z nějakého důvodu selže – zbytek programu nebude proveden a skript bude ukončen s odpovídajícím chybovým kódem. (To předpokládá, že foo
vytiskne svou vlastní chybu, která je dostatečná k pochopení důvodu selhání)
Odpovědět
EDIT : Tato odpověď je špatná, ale zajímavá, takže ji nechám pro budoucnost odkaz.
Přidání !
do příkazu invertuje návratový kód.
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. # =========================================================== #
Komentáře
- Myslím, že to nesouvisí. Ve vašem příkladu chci znát výstupní kód
ls
, nikoli invertovat výstupní kódbogus_command
- Navrhuji tuto odpověď odvolat.
- Zdá se, že jsem ‚ jsem idiot. I ‚ Vlastně jsem to použil ve skriptu, než jsem si myslel, že to, co OP chce. Dobrá věc, nepoužil jsem to ‚ k ničemu důležitému