Jeg har to prosesser foo
og bar
, forbundet med et rør:
$ foo | bar
bar
går alltid ut av 0; Jeg er interessert i utgangskoden til foo
. Er det noen måte å komme til det?
Kommentarer
- stackoverflow.com/questions/1221833/…
Svar
bash
og zsh
har en matrixvariabel som har utgangsstatus for hvert element (kommando) i den siste rørledningen som ble utført av skallet.
Hvis du bruker bash
, kalles matrisen PIPESTATUS
(saken betyr noe!) og matriseindikatorene starter på null:
$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0
Hvis du bruker zsh
, kalles matrisen pipestatus
(saken betyr noe!) og arrayindeksene starter på en:
$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0
For å kombinere dem i en funksjon på en måte som ikke mister verdiene:
$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0
Kjør ovennevnte i bash
eller zsh
, og du får de samme resultatene; bare en av retval_bash
og retval_zsh
blir angitt. Den andre vil være blank. Dette vil tillate at en funksjon slutter med return $retval_bash $retval_zsh
(merk mangelen på anførselstegn!).
Kommentarer
- Og
pipestatus
i zsh. Dessverre har andre skjell ikke ‘ t denne funksjonen. - Merk: Arrays i zsh begynner kontraintuitivt ved indeks 1, så den ‘ s
echo "$pipestatus[1]" "$pipestatus[2]"
. - Du kan sjekke hele rørledningen slik:
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
- @ JanHudec: Kanskje du bør lese de fem første ordene i svaret mitt. Påpek også hvor spørsmålet ba om et eneste POSIX-svar.
- @ JanHudec: Det ble heller ikke merket POSIX. Hvorfor antar du at svaret må være POSIX? Det ble ikke spesifisert, så jeg ga et kvalifisert svar. Det er ikke noe galt med svaret mitt, pluss at det er flere svar for å ta opp andre saker.
Svar
Der er tre vanlige måter å gjøre dette på:
Pipefail
Den første måten er å sette alternativet pipefail
(ksh
, zsh
eller bash
). Dette er det enkleste og hva det gjør er å sette utgangsstatus $?
til utgangskoden til det siste programmet for å avslutte ikke-null (eller null hvis alle avsluttes vellykket).
$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1
$ PIPESTATUS
Bash har også en arrayvariabel kalt $PIPESTATUS
($pipestatus
i zsh
) som inneholder utgangsstatus for alle programmene i siste rørledning.
$ 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 bruke det tredje kommandoeksemplet for å få den spesifikke verdien i rørledningen du trenger.
Separate henrettelser
Dette er den mest uhåndterlige løsningen. Kjør hver kommando separat og fang statusen
$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1
Kommentarer
- Darn! Jeg skulle bare legge ut innlegg om PIPESTATUS.
- Som referanse er det flere andre teknikker diskutert i dette SO-spørsmålet: stackoverflow.com/questions/1221833/…
- @Patrick pipestatus-løsningen fungerer på bash, bare mer quastion i tilfelle jeg bruker ksh script tror du vi kan finne noe som ligner pipestatus? , (mens jeg ser pipestatus ikke støttes av ksh)
- @yael bruker jeg ikke ‘
ksh
, men fra et kort blikk på den ‘ s manpage, støtter den ikke ‘ t$PIPESTATUS
eller noe lignende. Det støtter alternativetpipefail
. - Jeg bestemte meg for å gå med pipefail da det lar meg få status for den mislykkede kommandoen her:
LOG=$(failed_command | successful_command)
Svar
Denne løsningen fungerer uten å bruke bash-spesifikke funksjoner eller midlertidige filer . Bonus: til slutt er exit status faktisk en exit status og ikke noen streng i en fil.
Situasjon:
someprog | filter
deg vil ha utgangsstatus fra someprog
og utdata fra filter
.
Her er løsningen min:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
resultatet av denne konstruksjonen er stdout fra filter
som stdout av konstruksjonen og utgangsstatus fra someprog
som utgangsstatus for konstruksjonen.
denne konstruksjonen fungerer også med enkel kommandogruppering {...}
i stedet for subshells (...)
. subshells har noen implikasjoner, blant annet en ytelseskostnad, som vi ikke trenger her. les den fine bash-håndboken for mer informasjon: 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
Dessverre krever bash-grammatikken mellomrom og semikolon for de krøllete selene slik at konstruksjonen blir mye mer romslig.
For resten av denne teksten vil jeg bruke subshell-varianten.
Eksempel someprog
og 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 $?
Eksempel på utdata:
filtered line1 filtered line2 filtered line3 42
Merk: barneprosessen arver de åpne filbeskrivelsene fra foreldrene. Det betyr at someprog
vil arve åpen filbeskrivelse 3 og 4. Hvis someprog
skriver til filbeskrivelse 3, blir det avslutningsstatus. Den virkelige utgangsstatusen blir ignorert fordi read
bare leser en gang.
Hvis du er bekymret for at someprog
kan skrive til filbeskrivelse 3 eller 4, så er det best å lukke filbeskrivelsene før du ringer til someprog
.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
exec 3>&- 4>&-
før someprog
lukker filbeskrivelsen før someprog
så for someprog
disse filbeskrivelsene eksisterer ganske enkelt ikke.
Det kan også skrives slik: someprog 3>&- 4>&-
Trinnvis forklaring av konstruksjonen:
( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1
Fra bunnen av:
- Et subshell opprettes med filbeskrivelse 4 omdirigert til stdout. Dette betyr at alt som skrives ut til filbeskrivelse 4 i underskallet, vil ende opp som stdout for hele konstruksjonen.
- Et rør opprettes og kommandoene til venstre (
#part3
) og høyre (#part2
) kjøres.exit $xs
er også den siste kommandoen til røret, og det betyr at strengen fra stdin vil være utgangsstatus for hele konstruksjonen. - En subshell opprettes med fil deskriptor 3 omdirigert til stdout. Dette betyr at det som skrives ut til filbeskrivelse 3 i dette underskallet, vil ende opp i
#part2
og i sin tur være utgangsstatus for hele konstruksjonen. - A rør opprettes og kommandoene til venstre (
#part5
og#part6
) og høyre (filter >&4
) blir utført. Utdata frafilter
blir omdirigert til filbeskrivelse 4. I#part1
ble filbeskrivelsen 4 omdirigert til stdout. Dette betyr at utgangen frafilter
er stdout for hele konstruksjonen. - Avsluttningsstatus fra
#part6
skrives ut til filbeskrivelse 3. I#part3
ble filbeskrivelse 3 omdirigert til#part2
. Dette betyr at utgangsstatusen fra#part6
vil være den endelige utgangsstatusen for hele konstruksjonen. -
someprog
er henrettet. Utgangsstatusen er tatt i#part5
. Stdout blir tatt av røret i#part4
og videresendt tilfilter
. Utgangen frafilter
når igjen stdout som forklart i#part4
Kommentarer
- Hyggelig. For funksjonen kan jeg bare gjøre
(read; exit $REPLY)
-
(exec 3>&- 4>&-; someprog)
forenkler tilsomeprog 3>&- 4>&-
. - Denne metoden fungerer også uten underskall:
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Svar
Selv om du ikke er akkurat det du spurte, kan du bruke
#!/bin/bash -o pipefail
slik at rørene dine returnerer den siste returen uten null.
kan være litt mindre koding
Rediger: Eksempel
[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
inne i skriptet skal være mer robust, f.eks. i tilfelle noen utfører skriptet viabash foo.sh
. - Hvordan fungerer det? har du et eksempel?
- Merk at
-o pipefail
ikke er i POSIX. - Dette fungerer ikke i min BASH 3.2.25 ( 1) -utgivelse. På toppen av / tmp / ff har jeg
#!/bin/bash -o pipefail
.Feil er:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
- @FelipeAlvarez: Noen miljøer (inkludert Linux) ikke ‘ t analyserer mellomrom på
#!
linjer utover den første, og slik blir dette/bin/bash
-o pipefail
/tmp/ff
, i stedet for nødvendig/bin/bash
-o
pipefail
/tmp/ff
–getopt
(eller lignende) parsing ved hjelp avoptarg
, som er neste element iARGV
, som argumentet til-o
, så det mislykkes. Hvis du skulle lage en innpakning (sibash-pf
som bare gjordeexec /bin/bash -o pipefail "$@"
, og sette den på#!
linje, det ville fungere. Se også: no.wikipedia.org/wiki/Shebang_%28Unix%29
Svar
Det jeg gjør når det er mulig, er å mate utgangskoden fra foo
til bar
. Hvis jeg for eksempel vet at foo
aldri produserer en linje med bare sifre, så kan jeg bare slå på utgangskoden:
{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}"
Eller hvis jeg vet at utgangen fra foo
aldri inneholder en linje med bare .
:
{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}"
Dette kan alltid gjøres hvis det er noen måte å få bar
å jobbe på alle bortsett fra den siste linjen, og videreføre den siste linjen som utgangskode.
Hvis bar
er en kompleks rørledning hvis utdata du trenger du ikke, kan du omgå en del av den ved å skrive ut utgangskoden på en annen filbeskrivelse.
exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1)
Etter dette $exit_codes
er vanligvis foo:X bar:Y
, men det kan være bar:Y foo:X
hvis bar
slutter før du leser alle innspillene, eller hvis du er uheldig. Jeg tror at skriv til rør på opptil 512 byte er atomare på alle enheter, så foo:$?
og bar:$?
delene blir ikke blandet som så lenge tagstrengene er under 507 byte.
Hvis du trenger å fange utdata fra bar
, blir det vanskelig. Du kan kombinere teknikkene ovenfor ved å arrangere for at utgangen av bar
aldri skal inneholde en linje som ser ut som en utgangskodeindikasjon, 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")
Og selvfølgelig er det det enkle alternativet ved hjelp av en midlertidig fil for å lagre statusen. Enkelt, men ikke så enkelt i produksjon:
- Hvis det er flere skript som kjører samtidig, eller hvis det samme skriptet bruker denne metoden flere steder, må du lage at de bruker forskjellige midlertidige filnavn.
- Det er vanskelig å lage en midlertidig fil sikkert i en delt katalog. Ofte er
/tmp
det eneste stedet der et skript sikkert er i stand til å skrive filer. Brukmktemp
, som ikke er POSIX, men tilgjengelig på alle seriøse enheter i dag.
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 jeg bruker den midlertidige filtilnærmingen, foretrekker jeg å legge til en felle for EXIT som fjerner alle midlertidige filer slik at ingen søppel blir igjen selv om skriptet dør
Svar
Starter fra rørledningen:
foo | bar | baz
Her er en generell løsning som bare bruker POSIX-skall og ingen midlertidige 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
inneholder statuskodene til mislykkede prosesser, i tilfeldig rekkefølge, med indekser for å fortelle hvilken kommando som sendte ut hver 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
Legg merke til sitatene rundt $error_statuses
i testene mine; uten dem kan grep
ikke skille seg fordi de nye linjene blir tvunget til mellomrom.
Svar
Hvis du har moreutils -pakken installert, kan du bruke verktøyet feilrør som gjør akkurat det du spurte.
Svar
Så jeg ønsket å bidra med et svar som lesmana «s, men jeg tror min er kanskje litt enklere og litt mer fordelaktig 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.
Jeg tror dette er best forklart fra innsiden og ut – command1 vil utføre og skrive ut den vanlige utgangen på stdout (filbeskrivelse 1) , så når det er gjort, vil printf utføre og skrive ut kommandos avslutningskode på stdout, men stdout blir omdirigert til filbeskrivelse 3.
Mens command1 kjører, blir stdout piped til command2 (printf «s output gjør det aldri til command2 fordi vi sender det til filbeskrivelse 3 i stedet for 1, som jeg s hva røret leser).Deretter omdirigerer vi command2 «s utdata til filbeskrivelse 4, slik at den også holder seg utenfor filbeskrivelse 1 – fordi vi vil ha filbeskrivelse 1 gratis litt senere, fordi vi vil bringe printf-utdata på filbeskrivelse 3 tilbake ned i filbeskrivelse 1 – fordi det er hva kommandosubstitusjonen (backticks) vil fange, og at det er det som blir plassert i variabelen.
Den siste biten av magi er den første exec 4>&1
vi gjorde som en egen kommando – den åpner filbeskrivelse 4 som en kopi av det eksterne skallets stdout. Kommandosubstitusjon vil fange det som er skrevet på standard ut fra perspektivet til kommandoene inne i det – men siden kommando2s utgang kommer til filbeskrivelse 4 når det gjelder kommandosubstitusjonen, vil kommandosubstitusjonen ikke fange den – når den først kommer ut av kommandosubstitusjonen, går den faktisk fremdeles til skriptets generelle filbeskrivelse 1.
(exec 4>&1
har å være en egen kommando fordi mange vanlige skall ikke liker det når du prøver å skrive til en filbeskrivelse inne i en kommandosubstitusjon, som åpnes i kommandoen «ekstern» som bruker erstatningen. Så dette er den enkleste bærbare måten for å gjøre det.)
Du kan se på det på en mindre teknisk og mer leken måte, som om utgangene til kommandoene hopper over hverandre: kommando1 rør til kommando2, så hopper printfs utgang over kommando 2 slik at kommando2 ikke fanger den, og deretter hopper kommandos utgang over og ut av denne e kommandosubstitusjon akkurat som printf lander akkurat i tide for å bli fanget av substitusjonen slik at den havner i variabelen, og command2s utgang fortsetter på sin glade måte til standardutgangen, akkurat som i et vanlig rør.
Også, slik jeg forstår det, vil $?
fortsatt inneholde returkoden til den andre kommandoen i røret, fordi variable tilordninger, kommandosubstitusjoner og sammensatte kommandoer er alt effektivt gjennomsiktig for returkoden til kommandoen inne i dem, så returstatusen for kommando2 skal bli spredt ut – dette, og ikke å måtte definere en tilleggsfunksjon, er hvorfor jeg tror dette kan være en noe bedre løsning enn den som er foreslått av lesmana.
I henhold til advarslene lesmana nevner, er det mulig at kommando 1 på et eller annet tidspunkt vil ende opp med å bruke filbeskrivere 3 eller 4, så for å være mer robust, vil du gjøre:
exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&-
Merk at jeg bruker sammensatte kommandoer i eksemplet mitt, men subshells (bruker ( )
i stedet for { }
vil også fungere, men kan være mindre effektivt.)
Kommandoer arver filbeskrivere fra prosessen som lanserer dem, så hele den andre linjen vil arve filbeskrivelsen fire, og den sammensatte kommandoen etterfulgt av 3>&1
vil arve filbeskrivelsen tre. Så 4>&-
sørger for at den indre sammensatte kommandoen ikke vil arve filbeskrivelsen fire, og 3>&-
vil ikke arve filbeskrivelsen tre, så command1 får et «renere», mer standard miljø. Du kan også flytte det indre 4>&-
ved siden av 3>&-
, men jeg skjønner hvorfor ikke bare begrense omfanget så mye som mulig.
Jeg er ikke sikker på hvor ofte ting bruker filbeskrivelse tre og fire direkte – jeg tror det meste av tiden programmer bruker syscalls som returnerer ikke-brukte-for-øyeblikket filbeskrivere, men noen ganger skriver kode til fil antar jeg deskriptor 3 direkte (jeg kan tenke meg et program som sjekker en filbeskrivelse for å se om den er åpen, og bruke den hvis den er, eller oppføre seg annerledes hvis den ikke er). Så sistnevnte er sannsynligvis best å beholde i tankene og brukes til generelle saker.
Kommentarer
- Ser interessant ut, men jeg kan ‘ t finner helt ut hva du forventer at denne kommandoen skal gjøre, og datamaskinen min kan ‘ t, enten; jeg får
-bash: 3: Bad file descriptor
. - @ G-Man Høyre, jeg glemmer stadig at bash ikke aner hva det ‘ gjør når det co mes til filbeskrivere, i motsetning til skjellene jeg vanligvis bruker (asken som følger med busybox). Jeg ‘ forteller deg når jeg tenker på en løsning som gjør bash lykkelig. I mellomtiden, hvis du ‘ har en debian-boks hendig, kan du prøve den i dash, eller hvis du ‘ har fått travelbox til kan prøve det med den opptatte esken / sh.
- @ G-Man Når det gjelder hva jeg forventer at kommandoen skal gjøre, og hva den gjør i andre skall, er å omdirigere stdout fra kommando1 så den gjør ikke ‘ t blir fanget av kommandosubstitusjonen, men en gang utenfor kommandosubstitusjonen faller den fd3 tilbake til stdout slik at den ‘ setter rør som forventet å kommandere2.Når kommando 1 avsluttes, utløser printf og skriver ut utgangsstatus, som blir fanget inn i variabelen av kommandosubstitusjonen. Veldig detaljert oversikt her: stackoverflow.com/questions/985876/tee-and-exit-status/… Også , den kommentaren din leste som om den var ment å være fornærmende?
- Hvor skal jeg begynne? (1) Jeg beklager hvis du følte deg fornærmet. «Ser interessant ut» var ment alvorlig; det ville være flott om noe så kompakt som det fungerte så bra som du forventet det. Utover det sa jeg ganske enkelt at jeg ikke forsto hva løsningen din skulle gjøre. Jeg har jobbet / lekt med Unix i lang tid (siden før Linux eksisterte), og hvis jeg ikke forstår noe, er det et rødt flagg som, kanskje, andre mennesker vil ikke forstå det heller, og at det trenger mer forklaring (IMNSHO). … (forts.)
- (forts.) … Siden vil du “… tenke… at [du] forstår omtrent alt mer enn den gjennomsnittlige personen ”, kanskje du bør huske at målet med Stack Exchange ikke er å være en kommandoskriftstjeneste, som kaster ut tusenvis av engangsløsninger til trivielt forskjellige spørsmål; men heller å lære folk hvordan de skal løse sine egne problemer. Og til det formål må du kanskje forklare ting godt nok til at en «gjennomsnittlig person» kan forstå det. Se på lesmanas svar for et eksempel på en utmerket forklaring. … (Forts.)
Svar
lesmanas løsning ovenfor kan også gjøres uten overhead av starter nestede underprosesser ved å bruke { .. }
i stedet (husk at denne formen for grupperte kommandoer alltid må avsluttes med semikolon). Noe som dette:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Jeg har sjekket denne konstruksjonen med dashversjon 0.5.5 og bash-versjon 3.2.25 og 4.2.42, så selv om noen skjell ikke støtter { .. }
gruppering, det er fortsatt POSIX-kompatibelt.
Kommentarer
- Dette fungerer veldig bra med de fleste skjell jeg ‘ har prøvd det med, inkludert NetBSD sh, pdksh, mksh, dash, bash. Imidlertid kan jeg ‘ ikke få det til å fungere med AT & T Ksh (93s +, 93u +) eller zsh (4.3.9, 5.2), selv med
set -o pipefail
i ksh eller et hvilket som helst antall sprinkletwait
kommandoer i begge. Jeg tror det kan, delvis i det minste være et analyseproblem for ksh, som om jeg holder meg til å bruke subshells, så fungerer det bra, men selv med enif
for å velge subshell-varianten for ksh, men la sammensatte kommandoer være for andre mislykkes det.
Svar
Følgende er ment som et tillegg til svaret til @Patrik, i tilfelle du ikke er i stand til å bruke en av de vanlige løsningene.
Dette svaret forutsetter følgende:
- Du har et skall som ikke vet om
$PIPESTATUS
ellerset -o pipefail
- Du vil bruke et rør for parallell kjøring, så ingen midlertidige filer.
- Du ikke vil ha ekstra rot rundt deg hvis du avbryter skriptet, muligens ved et plutselig strømbrudd.
- Denne løsningen skal være relativt enkel å følge og ren å lese.
- Du gjør ikke ønsker å introdusere flere subshells.
- Du kan ikke fikle med de eksisterende filbeskrivelsene, så stdin / out / err må ikke berøres (hvordan noensinne kan du introdusere noen nye midlertidig)
Ytterligere antagelser. Du kan kvitte deg med alt, men dette forsterker oppskriften for mye, så det dekkes ikke her:
- Alt du vil vite er at alle kommandoer i PIPE har utgangskode 0.
- Du trenger ikke ytterligere informasjon om sidebånd.
- Skallet ditt venter på at alle rørkommandoer skal komme tilbake.
Før: foo | bar | baz
, men dette returnerer bare utgangskoden til den siste kommandoen (baz
)
Ønsket: $?
må ikke være 0
(true), hvis noen av kommandoene i røret mislyktes
Etter:
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
Forklart:
- Det opprettes en tempfil med
mktemp
. Dette oppretter vanligvis umiddelbart en fil i/tmp
- Denne tempfilen blir deretter omdirigert til FD 9 for skriving og FD 8 for lesing
- Deretter blir tempfile blir umiddelbart slettet. Det forblir imidlertid åpent til begge FD-ene går ut av livet.
- Nå er røret startet. Hvert trinn legger bare til FD 9 hvis det var en feil.
-
wait
er nødvendig forksh
, fordiksh
annet ikke venter på at alle rørkommandoene skal fullføres. Vær imidlertid oppmerksom på at det er uønskede bivirkninger hvis noen bakgrunnsoppgaver er til stede, så jeg kommenterte det som standard.Hvis ventetiden ikke skader, kan du kommentere den. - Etterpå leses filens innhold. Hvis den er tom (fordi alt fungerte)
read
returnererfalse
, såtrue
indikerer en feil
Dette kan brukes som erstatning for plugin for en enkelt kommando og trenger bare følgende:
- Ubrukt FD 9 og 8
- En enkelt miljøvariabel som inneholder navnet på tempfilen
- Og dette oppskriften kan tilpasses til stort sett alle skall der ute som tillater IO-omdirigering
- Det er også ganske plattformagnostisk og trenger ikke ting som
/proc/fd/N
BUGs:
Dette skriptet har en feil i tilfelle /tmp
går tom for plass. Hvis du også trenger beskyttelse mot denne kunstige saken, du kan gjøre det som følger, men dette har ulempen at antallet 0
i 000
avhenger av antall kommandoer irør, så det er litt mer komplisert:
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"
Notater om bærbarhet:
-
ksh
og lignende skall som bare venter på den siste rørkommandoen trengerwait
ukommentert -
Det siste eksemplet bruker
printf "%1s" "$?"
i stedet forecho -n "$?"
fordi dette er mer bærbart. Ikke alle plattformer tolker-n
riktig. -
printf "$?"
vil også gjøre det, imidlertidprintf "%1s"
fanger noen hjørnesaker i tilfelle du kjører skriptet på en virkelig ødelagt plattform. (Les: hvis du tilfeldigvis programmerer iparanoia_mode=extreme
.) -
FD 8 og FD 9 kan være høyere på plattformer som støtter flere sifre. AFAIR et POSIX-samsvarende skall trenger bare å støtte enkle sifre.
-
Ble testet med Debian 8.2
sh
,bash
,ksh
,ash
,sash
og til og medcsh
Svar
Dette er bærbart, dvs. fungerer med et hvilket som helst POSIX-kompatibelt skall, krever ikke at den nåværende katalogen er skrivbar og lar flere skript med samme triks kjøre samtidig.
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
Rediger: her er en sterkere versjon etter Gilles «kommentarer:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: og her er en litt lettere variant etter dubiousjim-kommentar:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
Kommentarer
- Dette fungerer ikke ‘ av flere grunner. 1. Den midlertidige filen kan leses før den ‘ skrives. 2. Å lage en midlertidig fil i en delt katalog med et forutsigbart navn er usikker (triviell DoS, symlink race). 3. Hvis det samme skriptet bruker dette trikset flere ganger, bruker det ‘ alltid det samme filnavnet. For å løse 1, les filen etter at rørledningen er fullført. For å løse 2 og 3, bruk en midlertidig fil med et tilfeldig generert navn eller i en privat katalog.
- +1 Vel, $ {PIPESTATUS [0]} er lettere, men den grunnleggende ideen her fungerer hvis man vet om problemene som Gilles nevner.
- Du kan lagre noen subshells:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Jeg er enig i at ‘ er enklere med Bash, men i noen sammenhenger er det verdt det å vite hvordan man kan unngå Bash.
Svar
Med litt forsiktighet skal dette fungere:
foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status)
Kommentarer
- Hva med å rydde opp som jlliagre? Ikke la ‘ t la igjen en fil som heter foo-status?
- @Johan: Hvis du foretrekker mitt forslag, ikke ‘ t nøl med å stemme den opp;) I tillegg til å ikke forlate en fil, har den fordelen at flere prosesser kan kjøre dette samtidig, og den nåværende katalogen trenger ikke å være skrivbar.
Svar
Følgende «if» -blokk kjører bare hvis «command» lykkes:
if command; then # ... fi
Spesielt kan du kjøre noe slikt:
haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi
Som vil kjøre haconf -makerw
og lagre stdout og stderr til «$ haconf_out». Hvis den returnerte verdien fra haconf
er sant, vil «hvis» -blokken kjøres og grep
vil lese «$ haconf_out», og prøve for å matche den mot «Cluster allerede skrivbar».
Legg merke til at rør automatisk rydder seg opp; med viderekoblingen må du være forsiktig for å fjerne «$ haconf_out» når du er ferdig.
Ikke så elegant som pipefail
, men et legitimt alternativ hvis denne funksjonaliteten er ikke innen rekkevidde.
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 i det minste) kombinert med set -e
kan man bruke subshell til å eksplisitt etterligne pipefail og avslutte på pipe feil
set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program
Så hvis foo
mislykkes av en eller annen grunn – vil ikke resten av programmet kjøres og skriptet avsluttes med tilsvarende feilkode. (Dette forutsetter at foo
skriver ut sin egen feil, som er tilstrekkelig til å forstå årsaken til feilen)
Svar
EDIT : Dette svaret er galt, men interessant, så jeg lar det være for fremtiden referanse.
Når du legger til en !
i kommandoen, inverterer du 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
- Jeg tror dette ikke er relatert. I eksempelet ditt vil jeg vite utgangskoden til
ls
, ikke invertere utgangskoden tilbogus_command
- Jeg foreslår å trekke tilbake svaret.
- Vel, det ser ut til at jeg ‘ er en idiot. Jeg ‘ Vi har faktisk brukt dette i et skript før jeg trodde at det gjorde det OP ønsket. God ting jeg ikke brukte ‘ til noe viktig