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
- stackoverflow.com/questions/1221833/…
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 alternativetpipefail
. - 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:
- 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.
- 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. - 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. - A rör skapas och kommandona till vänster (
#part5
och#part6
) och höger (filter >&4
) körs. Utdata frånfilter
omdirigeras till filbeskrivare 4. I#part1
omdirigerades filbeskrivaren 4 till stdout. Detta betyder att utdata frånfilter
är stdout för hela konstruktionen. - 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. -
someprog
är avrättade. Utgångsstatusen tas i#part5
. Stdout tas av röret i#part4
och vidarebefordras tillfilter
. Utdata frånfilter
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 tillsomeprog 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 viabash 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/ff
–getopt
(eller liknande) tolkning medoptarg
, som är nästa objekt iARGV
, som argumentet för-o
, så det misslyckas. Om du skulle göra ett omslag (sägbash-pf
som bara gjordeexec /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 så 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ändmktemp
, 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 enif
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
ellerset -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örksh
, eftersomksh
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
returnerarfalse
, 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överwait
okommenterat -
Det sista exemplet använder
printf "%1s" "$?"
istället förecho -n "$?"
eftersom det här är mer portabelt. Inte alla plattformar tolkar-n
korrekt. -
printf "$?"
skulle göra det också, dockprintf "%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 iparanoia_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 medcsh
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örbogus_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