Få utgångsstatus för process som ' sänds till en annan

Jag har två processer foo och bar, anslutna till ett rör:

$ foo | bar 

bar lämnar alltid 0; Jag är intresserad av utgångskoden för foo. Finns det något sätt att komma till det?

Kommentarer

Svar

bash och zsh har en matrisvariabel som har utgångsstatus för varje element (kommando) i den sista pipelinen som körs av skalet.

Om du använder bash kallas matrisen PIPESTATUS

$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0 

Om du använder zsh, matrisen heter pipestatus (ärende är viktigt!) och arrayindexen börjar på en:

$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0 

För att kombinera dem inom en funktion på ett sätt som inte tappar värdena:

$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0 

Kör ovan i bash eller zsh och du får samma resultat; endast en av retval_bash och retval_zsh kommer att ställas in. Den andra kommer att vara tom. Detta skulle göra det möjligt för en funktion att sluta med return $retval_bash $retval_zsh (notera bristen på citat!).

Kommentarer

  • Och pipestatus i zsh. Tyvärr har andra skal inte ’ t den här funktionen.
  • Obs! Arrayer i zsh börjar kontraintuitivt vid index 1, så det ’ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Du kan kontrollera hela pipelinen så här: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @ JanHudec: Du kanske borde läsa de fem första orden i mitt svar. Påpeka även var frågan begärde ett enda POSIX-svar.
  • @ JanHudec: Det märktes inte heller POSIX. Varför antar du att svaret måste vara POSIX? Det specificerades inte så jag gav ett kvalificerat svar. Det finns inget fel i mitt svar, plus att det finns flera svar för att ta itu med andra fall.

Svar

Där är tre vanliga sätt att göra detta:

Pipefail

Det första sättet är att ställa in alternativet pipefail (ksh, zsh eller bash). Detta är det enklaste och vad det gör är att ställa in utgångsstatus $? till utgångskoden för det sista programmet för att avsluta icke-noll (eller noll om alla avslutades framgångsrikt).

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

$ PIPESTATUS

Bash har också en arrayvariabel som heter $PIPESTATUS ($pipestatus i zsh) som innehåller utgångsstatus för alla program i den sista pipelinen.

$ true | true; echo "${PIPESTATUS[@]}" 0 0 $ false | true; echo "${PIPESTATUS[@]}" 1 0 $ false | true; echo "${PIPESTATUS[0]}" 1 $ true | false; echo "${PIPESTATUS[@]}" 0 1 

Du kan använda det tredje kommandoexemplet för att få det specifika värde i rörledningen som du behöver.

Separata körningar

Detta är den mest otympliga lösningen. Kör varje kommando separat och fånga status

$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1 

Kommentarer

  • Darn! Jag skulle bara skriva om PIPESTATUS.
  • Som referens finns det flera andra tekniker som diskuteras i denna SO-fråga: stackoverflow.com/questions/1221833/…
  • @Patrick pipestatus-lösningen fungerar på bash, bara mer quastion om jag använder ksh-skript tror du att vi kan hitta något som liknar pipestatus? , (medan jag ser pipestatus inte stöds av ksh)
  • @yael Jag ’ t använder ksh, men från en kort blick på ’ s manpage stöder den inte ’ t $PIPESTATUS eller något liknande. Det stöder dock alternativet pipefail.
  • Jag bestämde mig för att gå med pipefail eftersom det tillåter mig att få status för det misslyckade kommandot här: LOG=$(failed_command | successful_command)

Svar

Den här lösningen fungerar utan bash-specifika funktioner eller tillfälliga filer . Bonus: i slutändan är utgångsstatusen faktiskt en utgångsstatus och inte någon sträng i en fil.

Situation:

someprog | filter 

du vill ha utgångsstatus från someprog och utdata från filter.

Här är min lösning:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

resultatet av denna konstruktion är stdout från filter som stdout av konstruktionen och utgångsstatus från someprog som konstruktions utgångsstatus.


denna konstruktion fungerar också med enkel kommandogruppering {...} istället för subshells (...). subshells har vissa konsekvenser, bland annat en prestandakostnad, som vi inte behöver här. läs den fina bash-manualen för mer information: 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 

Tyvärr kräver bash-grammatiken mellanslag och semikolon för de lockiga hakparenteserna så att konstruktionen blir mycket rymligare.

För resten av denna text kommer jag att använda subshell-varianten.


Exempel someprog och 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 $? 

Exempel på utdata:

filtered line1 filtered line2 filtered line3 42 

Obs: barnprocessen ärver de öppna filbeskrivarna från föräldern. Det betyder att someprog kommer att ärva öppen filbeskrivare 3 och 4. Om someprog skriver till filbeskrivare 3 blir det utgångsstatus. Den verkliga utgångsstatusen ignoreras eftersom read bara läser en gång.

Om du oroar dig för att din someprog kanske skriver till filbeskrivare 3 eller 4 är det bäst att stänga filbeskrivarna innan du ringer till someprog.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

exec 3>&- 4>&- innan someprog stänger filbeskrivaren innan someprog körs så för someprog dessa filbeskrivare existerar helt enkelt inte.

Det kan också skrivas så här: someprog 3>&- 4>&-


Steg för steg förklaring av konstruktionen:

( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1 

Uppifrån och upp:

  1. En subshell skapas med filbeskrivaren 4 omdirigeras till stdout. Det betyder att allt som skrivs ut i filbeskrivaren 4 i subshell kommer att sluta som stdout för hela konstruktionen.
  2. Ett rör skapas och kommandona till vänster (#part3) och höger (#part2) körs. exit $xs är också det sista kommandot för röret och det betyder att strängen från stdin kommer att vara utgångsstatus för hela konstruktionen.
  3. En subshell skapas med filen deskriptor 3 omdirigeras till stdout. Det betyder att vad som helst som skrivs ut till filbeskrivare 3 i denna subshell hamnar i #part2 och i sin tur kommer att vara utgångsstatus för hela konstruktionen.
  4. A rör skapas och kommandona till vänster (#part5 och #part6) och höger (filter >&4) körs. Utdata från filter omdirigeras till filbeskrivare 4. I #part1 omdirigerades filbeskrivaren 4 till stdout. Detta betyder att utdata från filter är stdout för hela konstruktionen.
  5. Utgångsstatus från #part6 skrivs ut till filbeskrivare 3. I #part3 omdirigerades filbeskrivare 3 till #part2. Detta innebär att utgångsstatus från #part6 blir den slutliga utgångsstatusen för hela konstruktionen.
  6. someprog är avrättade. Utgångsstatusen tas i #part5. Stdout tas av röret i #part4 och vidarebefordras till filter. Utdata från filter når i sin tur stdout som förklaras i #part4

Kommentarer

  • Trevligt. För funktionen kan jag bara göra (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) förenklar till someprog 3>&- 4>&-.
  • Den här metoden fungerar även utan subshells: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Svar

Även om det inte var precis vad du frågade, kan du använda

#!/bin/bash -o pipefail 

så att dina rör returnerar den sista returen utan noll.

kan vara lite mindre kodande

Redigera: Exempel

[root@localhost ~]# false | true [root@localhost ~]# echo $? 0 [root@localhost ~]# set -o pipefail [root@localhost ~]# false | true [root@localhost ~]# echo $? 1 

Kommentarer

  • set -o pipefail inuti skriptet bör vara mer robust, t.ex. om någon kör skriptet via bash foo.sh.
  • Hur fungerar det? har du ett exempel?
  • Observera att -o pipefail inte finns i POSIX.
  • Detta fungerar inte i min BASH 3.2.25 ( 1) -släpp. Överst på / tmp / ff har jag #!/bin/bash -o pipefail.Fel är: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: Vissa miljöer (inklusive Linux) inte ’ t analyserar mellanslag på #! rader bortom den första, och så blir detta /bin/bash -o pipefail /tmp/ff istället för nödvändigt /bin/bash -o pipefail /tmp/ffgetopt (eller liknande) tolkning med optarg, som är nästa objekt i ARGV, som argumentet för -o, så det misslyckas. Om du skulle göra ett omslag (säg bash-pf som bara gjorde exec /bin/bash -o pipefail "$@" och sätt det på #! rad, det skulle fungera. Se även: sv.wikipedia.org/wiki/Shebang_%28Unix%29

Svar

Vad jag gör när det är möjligt är att mata utgångskoden från foo till bar. Om jag till exempel vet att foo aldrig producerar en rad med bara siffror, så kan jag bara slå på utgångskoden:

{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}" 

Eller om jag vet att utdata från foo aldrig innehåller en rad med bara .:

{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}" 

Detta kan alltid göras om det finns något sätt att få bar att arbeta på alla utom den sista raden och vidarebefordra den sista raden som dess utgångskod.

Om bar är en komplex pipeline vars utdata du behöver inte, du kan kringgå en del av den genom att skriva ut utgångskoden på en annan filbeskrivare.

exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1) 

Efter detta $exit_codes är vanligtvis foo:X bar:Y, men det kan vara bar:Y foo:X om bar slutar innan du läser alla inmatningar eller om du är otur. Jag tror att skrivningar till rör på upp till 512 byte är atomära på alla enheter, så att foo:$? och bar:$? delar kommer inte att blandas som så länge tagsträngarna är under 507 byte.

Om du behöver fånga utdata från bar blir det svårt. Du kan kombinera teknikerna ovan genom att ordna för att utdata från bar aldrig ska innehålla en rad som ser ut som en utgångskodindikering, men den blir 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") 

Och det är naturligtvis det enkla alternativet att använder en tillfällig fil för att lagra status. Enkelt men inte enkelt i produktion:

  • Om det finns flera skript som körs samtidigt, eller om samma skript använder den här metoden på flera ställen, måste du göra se till att de använder olika tillfälliga filnamn.
  • Det är svårt att skapa en tillfällig fil säkert i en delad katalog. Ofta är /tmp den enda platsen där ett skript säkert kan skriva filer. Använd mktemp , vilket inte är POSIX men tillgängligt på alla seriösa enheter nuförtiden.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Kommentarer

  • När jag använder den tillfälliga filmetoden föredrar jag att lägga till en fälla för EXIT som tar bort alla tillfälliga filer så att inget skräp lämnas även om skriptet dör

Svar

Från och med pipeline:

foo | bar | baz 

Här är en allmän lösning som endast använder POSIX-skal och inga tillfälliga filer:

exec 4>&1 error_statuses="`((foo || echo "0:$?" >&3) | (bar || echo "1:$?" >&3) | (baz || echo "2:$?" >&3)) 3>&1 >&4`" exec 4>&- 

$error_statuses innehåller statuskoder för alla misslyckade processer, i slumpmässig ordning, med index för att ange vilket kommando som avger varje 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 

Observera citaten runt $error_statuses i mina tester; utan dem grep kan inte skilja sig åt eftersom de nya raderna tvingas till mellanslag.

Svar

Om du har moreutils -paketet installerat kan du använda verktyget felrör som gör exakt vad du frågade.

Svar

Så jag ville bidra med ett svar som lesmana, men jag tror att min kanske är lite enklare och lite mer fördelaktig ren- Bourne-shell-lösning:

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

Jag tror att detta förklaras bäst från insidan och ut – command1 kommer att utföra och skriva ut dess vanliga utdata på stdout (filbeskrivare 1) , så när det är klart kommer printf att köra och skriva ut kommandos utgångskod på sin stdout, men den stdout omdirigeras till filbeskrivaren 3.

Medan command1 körs, skickas dess stdout till command2 (printf: s utgång gör det aldrig till command2 eftersom vi skickar det till filbeskrivaren 3 istället för 1, som jag är vad röret läser).Sedan omdirigerar vi kommando2: s utdata till filbeskrivaren 4, så att den också förblir utanför filbeskrivaren 1 – för att vi vill ha filbeskrivaren 1 gratis för lite senare, eftersom vi kommer att föra ut printf-utdata på filbeskrivare 3 tillbaka till filbeskrivare 1 – eftersom det är vad kommandosubstitutionen (backticks) kommer att fånga och det är vad som kommer att placeras i variabeln.

Den sista biten av magi är den första exec 4>&1 gjorde vi som ett separat kommando – det öppnar filbeskrivaren 4 som en kopia av det externa skalets stdout. Kommandosubstitution kommer att fånga allt som skrivs på standard ut ur kommandot inuti perspektivet – men eftersom kommando2: s utdata kommer till filbeskrivare 4 vad gäller kommandosubstitutionen, kommer kommandosubstitutionen inte att fånga den – när det väl kommit ut ur kommandosubstitutionen går det fortfarande till skriptets övergripande filbeskrivare 1.

(exec 4>&1 har att vara ett separat kommando eftersom många vanliga skal inte gillar det när du försöker skriva till en filbeskrivare inuti en kommandosubstitution, som öppnas i kommandot ”externt” som använder substitutionen. Så detta är det enklaste bärbara sättet för att göra det.)

Du kan titta på det på ett mindre tekniskt och mer lekfullt sätt, som om utmatningarna från kommandona hoppar över varandra: kommando1 rör till kommando2, sedan hoppar printf: s utdata över kommando 2 så att kommando2 inte fångar det, och sedan hoppar kommando 2: s utgång över och ut ur detta Kommandosubstitutionen precis som printf landar precis i tid för att fångas av substitutionen så att den hamnar i variabeln och kommando2: s utdata går på sin glada väg att skrivas till standardutgången, precis som i ett normalt rör.

Dessutom, som jag förstår det, kommer $? fortfarande att innehålla returkoden för det andra kommandot i röret, eftersom variabla tilldelningar, kommandosubstitutioner och sammansatta kommandon är allt effektivt transparent för returkoden för kommandot inuti dem, så returstatus för kommando2 bör spridas ut – detta, och inte behöva definiera en ytterligare funktion, är därför jag tror att detta kan vara en något bättre lösning än den som föreslås av lesmana.

Enligt de förbehåll som lesmana nämner är det möjligt att kommando 1 någon gång kommer att hamna med filbeskrivare 3 eller 4, så för att vara mer robust skulle du göra:

exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&- 

Observera att jag använder sammansatta kommandon i mitt exempel, men subshells (med ( ) istället för { } fungerar också, men kan kanske vara mindre effektivt.)

Kommandon ärver filbeskrivare från processen som startar dem, så att hela andra raden ärver filbeskrivaren fyra, och det sammansatta kommandot följt av 3>&1 kommer att ärva filbeskrivaren tre. Så 4>&- ser till att det inre sammansatta kommandot inte ärver filbeskrivaren fyra, och 3>&- kommer inte att ärva filbeskrivaren tre, så command1 får en ”renare”, mer standardmiljö. Du kan också flytta den inre 4>&- bredvid 3>&-, men jag tänker varför inte bara begränsa dess omfång så mycket som möjligt.

Jag är inte säker på hur ofta saker använder filbeskrivare tre och fyra direkt – jag tror att de flesta av programmen använder syscalls som returnerar icke-använda-för tillfället filbeskrivare, men ibland skriver kod till fil antar jag (jag kan tänka mig att ett program kontrollerar en filbeskrivare för att se om den är öppen och använder den om den är, eller om den beter sig annorlunda om den inte är). Så det är förmodligen bäst att behålla tänk på och använd i allmänna fall.

Kommentarer

  • Ser intressant ut, men jag kan ’ t räkna helt ut vad du förväntar dig att detta kommando ska göra, och min dator kan ’ t, antingen; jag får -bash: 3: Bad file descriptor.
  • @ G-Man Rätt, jag fortsätter att glömma att bash inte har någon aning om vad den ’ gör när den co mes till filbeskrivare, till skillnad från de skal jag brukar använda (askan som kommer med upptagen låda). Jag ’ meddelar dig när jag tänker på en lösning som gör bash lycklig. Under tiden om du ’ har en debianbox till hands kan du prova den i streck, eller om du ’ har upptagenbox till hands kan prova det med den upptagna askan / sh.
  • @ G-Man Vad jag förväntar mig att kommandot ska göra, och vad det gör i andra skal, är att omdirigera stdout från kommando1 så att det inte ’ t fångas av kommandosubstitutionen, men en gång utanför kommandosubstitutionen faller den fd3 tillbaka till stdout så att den ’ sätts som förväntat att kommandot2.När kommando 1 avslutas, utlöses printf och skriver ut dess utgångsstatus, som fångas in i variabeln genom kommandosubstitutionen. Mycket detaljerad uppdelning här: stackoverflow.com/questions/985876/tee-and-exit-status/… Också , den kommentaren din läste som om den var tämligen förolämpande?
  • Var ska jag börja? (1) Jag är ledsen om du kände dig förolämpad. ”Ser intressant ut” menades med allvar; det skulle vara jättebra om något så kompakt som det fungerade lika bra som du förväntade dig. Utöver det sa jag helt enkelt att jag inte förstod vad din lösning skulle göra. Jag har arbetat / spelat med Unix under lång tid (sedan innan Linux fanns), och om jag inte förstår något är det en röd flagga som, kanske, andra människor kommer inte att förstå det heller, och att det behöver mer förklaring (IMNSHO). … (Fortsättning)
  • (Fortsatt) … Eftersom du “… gillar att tro … att [du] förstår nästan allt mer än en genomsnittlig person ”, kanske du bör komma ihåg att målet med Stack Exchange inte är att vara en kommandotjänst som slår ut tusentals engångslösningar till trivialt distinkta frågor; utan snarare att lära människor hur man löser sina egna problem. Och, för detta ändamål, kanske du måste förklara saker tillräckligt bra för att en ”genomsnittlig person” kan förstå det. Titta på lesmanas svar för ett exempel på en utmärkt förklaring. … (Fortsättning)

Svar

lesmanas lösning ovan kan också göras utan kostnaden för starta kapslade underprocesser genom att använda { .. } istället (kom ihåg att denna form av grupperade kommandon alltid måste avslutas med semikolon). Något liknande:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1 

Jag har kontrollerat denna konstruktion med streckversion 0.5.5 och bash version 3.2.25 och 4.2.42, så även om vissa skal inte stöder { .. } gruppering, det är fortfarande POSIX-kompatibelt.

Kommentarer

  • Detta fungerar riktigt bra med de flesta skal jag ’ har provat det med, inklusive NetBSD sh, pdksh, mksh, dash, bash. Men jag kan ’ inte få det att fungera med AT & T Ksh (93s +, 93u +) eller zsh (4.3.9, 5.2), även med set -o pipefail i ksh eller valfritt antal strö wait kommandon i endera. Jag tror det kan, delvis åtminstone, vara en tolkningsfråga för ksh, som om jag håller mig till att använda subshells så fungerar det bra, men även med en if för att välja subshell-varianten för ksh men lämna kompositionerna för andra misslyckas det.

Svar

Följande menas som ett tillägg till svaret från @Patrik, om du inte kan använda någon av de vanliga lösningarna.

Detta svar förutsätter följande:

  • Du har ett skal som inte känner till $PIPESTATUS eller set -o pipefail
  • Du vill använda ett rör för parallell körning, så inga tillfälliga filer.
  • Du vill inte ha mer röran om du avbryter skriptet, eventuellt genom ett plötsligt strömavbrott.
  • Denna lösning ska vara relativt lätt att följa och ren att läsa.
  • Du gör vill inte införa ytterligare subshells.
  • Du kan inte fitta med befintliga filbeskrivare, så stdin / out / err får inte beröras (hur du kan introducera någon ny tillfälligt)

Ytterligare antaganden. Du kan bli av med allt, men detta klumpar receptet för mycket, så det täcks inte här:

  • Allt du vill veta är att alla kommandon i PIPE har utgångskod 0.
  • Du behöver inte ytterligare sidbandinformation.
  • Ditt skal väntar på att alla rörkommandon ska återvända.

Före: foo | bar | baz, men detta returnerar endast utgångskoden för det sista kommandot (baz)

Önskas: $? får inte vara 0 (true), om någon av kommandona i röret misslyckades

Efter:

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 

Förklaras:

  • En tempfil skapas med mktemp. Detta skapar vanligtvis omedelbart en fil i /tmp
  • Denna tempfil omdirigeras sedan till FD 9 för skrivning och FD 8 för läsning
  • Sedan tempfile raderas omedelbart. Det förblir dock öppet tills båda FD: erna försvinner.
  • Nu är röret startat. Varje steg lägger bara till FD 9 om det uppstod ett fel.
  • wait behövs för ksh, eftersom ksh annars inte väntar på att alla rörkommandon ska slutföras. Observera dock att det finns oönskade sideffekter om vissa bakgrundsuppgifter är närvarande, så jag kommenterade det som standard.Om väntetiden inte gör ont kan du kommentera det.
  • Därefter läses filens innehåll. Om det är tomt (eftersom allt fungerade) read returnerar false, så true indikerar ett fel

Detta kan användas som en plugin-ersättning för ett enda kommando och behöver bara följa:

  • Oanvända FD 9 och 8
  • En enda miljövariabel för att hålla namnet på tempfilen
  • Och detta recept kan anpassas till i stort sett vilket skal som helst som möjliggör omdirigering av IO
  • Det är också ganska agnostiskt plattform och behöver inte saker som /proc/fd/N

BUG:

Det här skriptet har ett fel om /tmp tar slut på utrymmet. Om du behöver skydd mot det här konstgjorda fallet också du kan göra det enligt följande, men detta har nackdelen att antalet 0 i 000 beror på antalet kommandon irör, så det är lite mer komplicerat:

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" 

Anteckningar om bärbarhet:

  • ksh och liknande skal som bara väntar på det sista rörkommandot behöver wait okommenterat

  • Det sista exemplet använder printf "%1s" "$?" istället för echo -n "$?" eftersom det här är mer portabelt. Inte alla plattformar tolkar -n korrekt.

  • printf "$?" skulle göra det också, dock printf "%1s" fångar några hörnfall om du kör skriptet på någon riktigt trasig plattform. (Läs: om du råkar programmera i paranoia_mode=extreme.)

  • FD 8 och FD 9 kan vara högre på plattformar som stöder flera siffror. AFAIR ett POSIX-kompatibelt skal behöver bara stödja enstaka siffror.

  • Testades med Debian 8.2 sh, bash, ksh, ash, sash och till och med csh

Svar

Detta är bärbart, dvs. fungerar med alla POSIX-kompatibla skal, kräver inte att den aktuella katalogen är skrivbar och låter flera skript med samma trick köras samtidigt.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$)) 

Redigera: här är en starkare version efter Gilles ”kommentarer:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s)) 

Edit2: och här är en lite lättare variant efter dubiousjim-kommentar:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)) 

Kommentarer

  • Detta fungerar inte ’ av flera skäl. 1. Den tillfälliga filen kan läsas innan den ’ skrivs. 2. Att skapa en tillfällig fil i en delad katalog med ett förutsägbart namn är osäkert (trivial DoS, symlink race). 3. Om samma skript använder detta trick flera gånger, använder det ’ alltid samma filnamn. För att lösa 1, läs filen efter att pipelinen har slutförts. För att lösa 2 och 3, använd en tillfällig fil med ett slumpmässigt genererat namn eller i en privat katalog.
  • +1 Jo $ {PIPESTATUS [0]} är enklare men grundidén här fungerar om man känner till de problem som Gilles nämner.
  • Du kan spara några subshells: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Jag håller med om att det ’ är lättare med Bash men i vissa sammanhang är det värt det att veta hur man undviker Bash.

Svar

Med lite försiktighet bör detta fungera:

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

Kommentarer

  • Vad sägs om att rensa som jlliagre? Lämna inte ’ om du lämnar en fil som heter foo-status?
  • @Johan: Om du föredrar mitt förslag, don ’ t tveka att rösta upp den;) Förutom att inte lämna en fil, har den fördelen att flera processer kan köras samtidigt och den aktuella katalogen behöver inte vara skrivbar.

Svar

Följande ”if” -block körs endast om ”command” lyckades:

if command; then # ... fi 

Specifikt kan du köra något så här:

haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi 

Vilket kör haconf -makerw och lagra dess stdout och stderr till ”$ haconf_out”. Om det returnerade värdet från haconf är sant kommer ”if” -blocket att köras och grep kommer att läsa ”$ haconf_out”, försöker för att matcha den mot ”Cluster redan skrivbar”.

Observera att rör automatiskt städar sig själva; med omdirigeringen måste du vara försiktig för att ta bort ”$ haconf_out” när du är klar.

Inte så elegant som pipefail, men ett legitimt alternativ om den här funktionen är inte inom räckhåll.

Svar

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 $ 

Svar

(Med bash åtminstone) i kombination med set -e kan man använda subshell för att uttryckligen emulera pipefail och avsluta rörfel

set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program 

Så om foo misslyckas av någon anledning – resten av programmet körs inte och skriptet avslutas med motsvarande felkod. (Detta förutsätter att foo skriver ut sitt eget fel, vilket är tillräckligt för att förstå orsaken till misslyckandet)

Svar

EDIT : Det här svaret är fel, men intressant, så jag lämnar det för framtiden referens.


Att lägga till en ! till kommandot inverterar returkoden.

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

Kommentarer

  • Jag tror att detta inte är relaterat. I ditt exempel vill jag veta utgångskoden för ls, inte invertera utgångskoden för bogus_command
  • Jag föreslår att jag drar tillbaka svaret.
  • Det verkar som om jag ’ är en idiot. Jag ’ Vi har faktiskt använt detta i ett skript innan vi trodde att det gjorde vad OP ville. Bra att jag inte ’ inte använde det för något viktigt

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *