Získejte stav ukončení procesu, který ' s vedl do jiného

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

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žnost pipefail.
  • 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:

  1. 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.
  2. 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.
  3. 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.
  4. A potrubí je vytvořeno a příkazy vlevo (#part5 a #part6) a vpravo (filter >&4) jsou provedeny. Výstup filter 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ýstup filter je standardním výstupem celého konstruktu.
  5. 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.
  6. 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 do filter. Výstup z filter 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 na someprog 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/ffgetopt (nebo podobné) zpracování pomocí optarg, což je další položka v ARGV, jako argument -o, takže selže. Pokud byste měli vytvořit obálku (řekněme bash-pf, která právě udělala exec /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žijte mktemp , 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ých wait 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ři if 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 ani set -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 pro ksh, protože ksh 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že true 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ísto echo -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 v paranoia_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 i csh

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ód bogus_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

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *