Jeg har to processer foo
og bar
, forbundet med et rør:
$ foo | bar
bar
afslutter altid 0; Jeg er interesseret i udgangskoden til foo
. Er der nogen måde at komme til det?
Kommentarer
- stackoverflow.com/questions/1221833/…
Svar
bash
og zsh
har en matrixvariabel der holder udgangsstatus for hvert element (kommando) i den sidste pipeline, der udføres af skallen.
Hvis du bruger bash
, kaldes arrayet PIPESTATUS
(sag betyder noget!) og matrixindikationerne starter ved nul:
$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0
Hvis du bruger zsh
kaldes arrayet pipestatus
(case betyder noget!) og array-indekserne starter ved en:
$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0
For at kombinere dem inden for en funktion på en måde, der ikke mister værdierne:
$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0
Kør ovenstående i bash
eller zsh
, og du får de samme resultater; kun en af retval_bash
og retval_zsh
indstilles. Den anden vil være blank. Dette vil gøre det muligt for en funktion at ende med return $retval_bash $retval_zsh
(bemærk manglen på anførselstegn!).
Kommentarer
- Og
pipestatus
i zsh. Desværre har andre skaller ‘ ikke denne funktion. - Bemærk: Arrays i zsh begynder kontraintuitivt ved indeks 1, så det ‘ s
echo "$pipestatus[1]" "$pipestatus[2]"
. - Du kunne kontrollere hele pipelinen sådan:
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
- @ JanHudec: Måske skulle du læse de første fem ord i mit svar. Påpeg venligst også, hvor spørgsmålet anmodede om et kun POSIX-svar.
- @ JanHudec: Det var heller ikke mærket POSIX. Hvorfor antager du, at svaret skal være POSIX? Det blev ikke specificeret, så jeg leverede et kvalificeret svar. Der er ikke noget forkert i mit svar, plus der er flere svar til andre sager.
Svar
Der er 3 almindelige måder at gøre dette på:
Pipefail
Den første måde er at indstille indstillingen pipefail
(ksh
, zsh
eller bash
). Dette er det enkleste, og hvad det gør, er grundlæggende at indstille udgangsstatus $?
til udgangskoden for det sidste program for at afslutte ikke-nul (eller nul hvis alle afsluttes med succes). p>
$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1
$ PIPESTATUS
Bash har også en array-variabel kaldet $PIPESTATUS
($pipestatus
i zsh
), som indeholder udgangsstatus for alle programmer i den sidste pipeline.
$ 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 bruge det 3. kommandoeksempel til at få den specifikke værdi i den pipeline, du har brug for.
Separate udførelser
Dette er den mest uhåndterlige løsning. Kør hver kommando separat, og indfang status
$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1
Kommentarer
- Darn! Jeg ville bare skrive om PIPESTATUS.
- Til reference er der flere andre teknikker diskuteret i dette SO-spørgsmål: stackoverflow.com/questions/1221833/…
- @Patrick pipestatus-løsningen fungerer på bash, bare mere quastion, hvis jeg bruger ksh-script, tror du, vi kan finde noget der ligner pipestatus? , (i mellemtiden ser jeg pipestatus ikke understøttet af ksh)
- @yael Jeg bruger ikke ‘
ksh
, men fra et kort blik på det ‘ s manpage understøtter det ikke ‘ t$PIPESTATUS
eller noget lignende. Det understøtter dog indstillingenpipefail
. - Jeg besluttede at gå med pipefail, da det giver mig mulighed for at få status på den mislykkede kommando her:
LOG=$(failed_command | successful_command)
Svar
Denne løsning fungerer uden at bruge bash-specifikke funktioner eller midlertidige filer . Bonus: i sidste ende er exit-status faktisk en exit-status og ikke en streng i en fil.
Situation:
someprog | filter
dig vil have exit status fra someprog
og output fra filter
.
Her er min løsning:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
resultatet af denne konstruktion er stdout fra filter
som stdout af konstruktionen og exit status fra someprog
som udgangsstatus for konstruktionen.
denne konstruktion fungerer også med simpel kommandogruppe {...}
i stedet for subshells (...)
. subshells har nogle implikationer, blandt andet en præstationsomkostning, som vi ikke har brug for her. læs den fine bash manual for flere detaljer: 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
Desværre kræver bash-grammatik mellemrum og semikoloner til de krøllede seler, så konstruktionen bliver meget mere rummelig.
For resten af denne tekst vil jeg bruge 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å output:
filtered line1 filtered line2 filtered line3 42
Bemærk: underordnet proces arver de åbne filbeskrivelser fra den overordnede. Det betyder, at someprog
arver den åbne filbeskrivelse 3 og 4. Hvis someprog
skriver til filbeskriveren 3, bliver det exitstatus. Den virkelige udgangsstatus ignoreres, fordi read
kun læser en gang.
Hvis du er bekymret for, at din someprog
måske skriver til filbeskrivelse 3 eller 4, så er det bedst at lukke filbeskrivelserne, 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
udføres, så for someprog
disse filbeskrivere findes simpelthen ikke.
Det kan også skrives sådan: someprog 3>&- 4>&-
Trin for trin forklaring af konstruktionen:
( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1
Fra bunden op:
- En subshell oprettes med filbeskrivelse 4 omdirigeret til stdout. Dette betyder, at uanset hvad der udskrives til filbeskrivelse 4 i subshell, ender det som stdout for hele konstruktionen.
- Et rør oprettes og kommandoerne til venstre (
#part3
) og højre (#part2
) udføres.exit $xs
er også den sidste kommando af røret, og det betyder, at strengen fra stdin vil være udgangsstatus for hele konstruktionen. - En subshell oprettes med fil deskriptor 3 omdirigeret til stdout. Dette betyder, at hvad som helst der er udskrevet til filbeskrivelse 3 i denne subshell, ender i
#part2
og igen vil være udgangsstatus for hele konstruktionen. - A rør oprettes, og kommandoerne til venstre (
#part5
og#part6
) udføres. Outputtet frafilter
omdirigeres til filbeskrivelse 4. I#part1
blev filbeskrivelsen 4 omdirigeret til stdout. Dette betyder, at output affilter
er stdout af hele konstruktionen.
#part6
udskrives til filbeskrivelse 3. I #part3
blev filbeskrivelse 3 omdirigeret til #part2
. Dette betyder, at udgangsstatus fra #part6
vil være den endelige udgangsstatus for hele konstruktionen. someprog
er henrettet. Afslutningsstatus tages i #part5
. Stdout tages af røret i #part4
og videresendes til filter
. Outputtet fra filter
når til gengæld stdout som forklaret i #part4
Kommentarer
- Dejligt. Til funktionen kan jeg bare gøre
(read; exit $REPLY)
-
(exec 3>&- 4>&-; someprog)
forenkler tilsomeprog 3>&- 4>&-
. - Denne metode fungerer også uden underskaller:
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Svar
Selvom det ikke er præcis, hvad du spurgte, kan du bruge
#!/bin/bash -o pipefail
, så dine rør returnerer det sidste ikke-nul-retur.
kan være lidt mindre kodende
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
inde i scriptet skal være mere robust, f.eks. hvis nogen udfører scriptet viabash foo.sh
. - Hvordan fungerer det? har du et eksempel?
- Bemærk at
-o pipefail
ikke er i POSIX. - Dette fungerer ikke i min BASH 3.2.25 ( 1) -frigivelse. Øverst på / tmp / ff har jeg
#!/bin/bash -o pipefail
.Fejl er:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
- @FelipeAlvarez: Nogle miljøer (inklusive linux) don ‘ t analyserer mellemrum på
#!
linjer ud over den første, og så bliver dette til/bin/bash
-o pipefail
/tmp/ff
i stedet for det nødvendige/bin/bash
-o
pipefail
/tmp/ff
–getopt
(eller lignende) parsing ved hjælp afoptarg
, som er det næste element iARGV
, som argumentet for-o
, så det mislykkes. Hvis du skulle lave en indpakning (sig,bash-pf
, der bare gjordeexec /bin/bash -o pipefail "$@"
, og læg den på#!
linje, det ville fungere. Se også: da.wikipedia.org/wiki/Shebang_%28Unix%29
Svar
Hvad jeg gør, når det er muligt, er at føde udgangskoden fra foo
til bar
Hvis jeg for eksempel ved, at foo
aldrig producerer en linje med bare cifre, så kan jeg bare tackle udgangskoden:
{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}"
Eller hvis jeg ved, at output fra foo
aldrig indeholder en linje med bare .
:
{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}"
Dette kan altid gøres, hvis der er en eller anden måde at få bar
at arbejde på alle undtagen den sidste linje og videresende den sidste linje som dens udgangskode.
Hvis bar
er en kompleks rørledning, hvis output du behøver du ikke, du kan omgå en del af den ved at udskrive exitkoden på en anden filbeskrivelse.
exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1)
Herefter $exit_codes
er normalt foo:X bar:Y
, men det kan være bar:Y foo:X
hvis bar
stopper inden du læser alle dens input, eller hvis du er uheldig. Jeg synes, at skriv til rør på op til 512 byte er atomare på alle enheder, så foo:$?
og bar:$?
delene bliver ikke blandet som så længe tagstrengene er under 507 bytes.
Hvis du har brug for at hente output fra bar
, bliver det svært. Du kan kombinere ovenstående teknikker ved at arrangere for at output fra bar
aldrig skal indeholde en linje, der ligner en udgangskodeindikation, men den bliver svimlende.
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 der den enkle mulighed for ved hjælp af en midlertidig fil til at gemme status. Enkelt, men ikke det simpelt i produktionen:
- Hvis der er flere scripts, der kører samtidigt, eller hvis det samme script bruger denne metode flere steder, skal du lave Sørg for, at de bruger forskellige midlertidige filnavne.
- Det er svært at oprette en midlertidig fil sikkert i et delt bibliotek. Ofte er
/tmp
det eneste sted, hvor et script helt sikkert er i stand til at skrive filer. Brugmktemp
, hvilket ikke er POSIX, men tilgængeligt på alle seriøse enheder 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 bruger den midlertidige filtilgang, foretrækker jeg at tilføje en fælde til EXIT, der fjerner alle midlertidige filer så der ikke efterlades noget affald, selvom scriptet dør
Svar
Startende fra rørledningen:
foo | bar | baz
Her er en generel løsning, der kun bruger POSIX-shell 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
indeholder statuskoder for eventuelle mislykkede processer i tilfældig rækkefølge med indekser til at fortælle, hvilken kommando der udsendte 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
Bemærk citaterne omkring $error_statuses
i mine tests; uden dem kan grep
ikke skelne, fordi de nye linjer tvinges til mellemrum.
Svar
Hvis du har moreutils -pakken installeret, kan du bruge værktøjet mispipe som gør nøjagtigt det, du spurgte.
Svar
Så jeg ville gerne bidrage med et svar som lesmana “s, men jeg synes min er måske lidt enklere og lidt mere fordelagtig 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 synes, det er bedst forklaret indefra og ud – command1 udfører og udskriver sin regelmæssige output på stdout (filbeskrivelse 1) , så når det er gjort, vil printf udføre og udskrive kommandos udgangskode på stdout, men den stdout omdirigeres til filbeskrivelse 3.
Mens command1 kører, ledes stdout til command2 (printfs output gør det aldrig til command2, fordi vi sender det til filbeskrivelse 3 i stedet for 1, som jeg s hvad røret læser).Derefter omdirigerer vi command2 “s output til fil deskriptor 4, så det også forbliver ude af file deskriptor 1 – fordi vi vil have fil deskriptor 1 gratis lidt senere, fordi vi bringer printf output på fil deskriptor 3 tilbage ned i filbeskrivelse 1 – fordi det er, hvad kommandosubstitutionen (backticks), vil fange, og at det er, hvad der bliver placeret i variablen.
Den sidste bit af magi er den første exec 4>&1
vi gjorde som en separat kommando – den åbner filbeskrivelse 4 som en kopi af den eksterne shells stdout. Kommandosubstitution vil fange alt, hvad der er skrevet på standard ud fra perspektivet af kommandoerne inde i det – men da kommandos2 output går til filbeskrivelse 4 for så vidt kommandosubstitutionen er, kan kommandosubstitutionen ikke fange det – når det først er “ude” af kommandosubstitutionen, går det faktisk stadig til scriptets overordnede filbeskrivelse 1.
(exec 4>&1
har at være en separat kommando, fordi mange almindelige skaller ikke kan lide det, når du prøver at skrive til en filbeskrivelse inde i en kommandosubstitution, der åbnes i den “eksterne” kommando, der bruger substitutionen. Så dette er den enkleste bærbare måde at gøre det.)
Du kan se på det på en mindre teknisk og mere legende måde, som om output fra kommandoerne springer hinanden: kommando1 rør til kommando2, så springer printfs output over kommando 2, så kommando2 ikke fanger den, og derefter springer kommando 2s output over og ud af th e kommandosubstitution, ligesom printf lander lige i tide for at blive fanget af substitutionen, så den ender i variablen, og command2s output går på sin glædelige måde til at blive skrevet til standardoutputtet, ligesom i et normalt rør.
Også, som jeg forstår det, vil $?
stadig indeholde returkoden for den anden kommando i røret, fordi variable tildelinger, kommandosubstitutioner og sammensatte kommandoer er alle effektivt gennemsigtige for returkoden for kommandoen indeni dem, så returneringsstatus for kommando2 skal udbredes – dette og ikke at skulle definere en ekstra funktion, er derfor, jeg tror, det kan være en noget bedre løsning end den foreslåede af lesmana.
Ifølge de forbehold, som lesmana nævner, er det muligt, at kommando1 på et eller andet tidspunkt ender med filbeskrivere 3 eller 4, så for at være mere robust, ville du gøre:
exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&-
Bemærk, at jeg bruger sammensatte kommandoer i mit eksempel, men subshells (ved hjælp af ( )
i stedet for { }
fungerer også, men kan måske være mindre effektiv.)
Kommandoer arver filbeskrivere fra processen der starter dem, så hele anden linje arver filbeskrivelse fire, og den sammensatte kommando efterfulgt af 3>&1
arver filbeskrivelsen tre. Så 4>&-
sørger for, at den indre sammensatte kommando ikke arver filbeskrivelse fire, og 3>&-
arver ikke filbeskrivelse tre, så command1 får et “renere”, mere standard miljø. Du kan også flytte det indre 4>&-
ved siden af 3>&-
, men jeg regner med hvorfor ikke bare begrænse dets omfang så meget som muligt.
Jeg er ikke sikker på, hvor ofte ting bruger filbeskrivelse tre og fire direkte – jeg tror, det meste af tiden bruger programmer syscalls, der returnerer ikke-brugt-i-øjeblik-filbeskrivere, men nogle gange skriver kode til fil Descriptor 3 direkte, antager jeg (jeg kunne forestille mig et program, der kontrollerer en fil deskriptor for at se, om det er åbent, og bruge det, hvis det er, eller opføre sig anderledes i overensstemmelse hermed, hvis det ikke er). Så sidstnævnte er sandsynligvis bedst at beholde i tankerne og bruges til almindelige sager.
Kommentarer
- Ser interessant ud, men jeg kan ‘ t finder ud af, hvad du forventer, at denne kommando skal gøre, og min computer kan ‘ t, enten; jeg får
-bash: 3: Bad file descriptor
. - @ G-Man Højre, jeg fortsætter med at glemme, at bash ikke har nogen idé om, hvad den ‘ laver, når den co mes til fil-deskriptorer, i modsætning til de skaller, jeg typisk bruger (asken, der følger med busybox). Jeg ‘ fortæller dig, når jeg tænker på en løsning, der gør bash lykkelig. I mellemtiden, hvis du ‘ har fået en debian-boks praktisk, kan du prøve den i bindestreg, eller hvis du ‘ har fået optaget box til dig kan prøve det med den travle boks aske / sh.
- @ G-Man Hvad jeg forventer, at kommandoen skal gøre, og hvad den gør i andre skaller, er at omdirigere stdout fra kommando1, så den ikke ‘ ikke blive fanget af kommandosubstitutionen, men når den er uden for kommandosubstitutionen, falder den fd3 tilbage til stdout, så den ‘ røres som forventet til kommando2.Når kommando 1 afsluttes, udskrives printf og udskriver dens exitstatus, som er fanget i variablen ved kommandosubstitution. Meget detaljeret opdeling her: stackoverflow.com/questions/985876/tee-and-exit-status/… Også , læses den kommentar fra dig, som om den skulle være lidt fornærmende?
- Hvor skal jeg begynde? (1) Jeg er ked af det, hvis du følte dig fornærmet. “Ser interessant ud” menes oprigtigt; det ville være godt, hvis noget så kompakt som det fungerede så godt, som du havde forventet det. Ud over det sagde jeg ganske enkelt, at jeg ikke forstod, hvad din løsning skulle gøre. Jeg har arbejdet / spillet med Unix i lang tid (siden før Linux eksisterede), og hvis jeg ikke forstår noget, er det et rødt flag, der måske andre mennesker forstår det heller ikke, og at det har brug for mere forklaring (IMNSHO). … (Fortsat)
- (Fortsat)… Da kan du “… tænke… at [du] forstår næsten alt mere end den gennemsnitlige person ”, måske skal du huske, at formålet med Stack Exchange ikke er at være en kommandoskrivertjeneste, der kæmper tusindvis af engangsløsninger til trivielt forskellige spørgsmål; men snarere at lære folk at løse deres egne problemer. Og til det formål er du måske nødt til at forklare ting godt nok til, at en “gennemsnitlig person” kan forstå det. Se på lesmanas svar for et eksempel på en fremragende forklaring. … (Fortsat)
Svar
lesmanas løsning ovenfor kan også udføres uden omkostningerne ved start indlejrede underprocesser ved at bruge { .. }
i stedet (husk at denne form for grupperede kommandoer altid skal slutte med semikolon). Noget som dette:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Jeg har tjekket denne konstruktion med bindestreg version 0.5.5 og bash version 3.2.25 og 4.2.42, så selvom nogle skaller ikke understøtter { .. }
gruppering, det er stadig POSIX-kompatibelt.
Kommentarer
- Dette fungerer rigtig godt med de fleste skaller I ‘ har prøvet det med, inklusive NetBSD sh, pdksh, mksh, dash, bash. Dog kan jeg ‘ ikke få det til at arbejde 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 antal dryssedewait
kommandoer i begge. Jeg tror, det kan til dels i det mindste være et analyseproblem for ksh, som om jeg holder mig til at bruge subshells, så fungerer det fint, men selv med enif
for at vælge subshell-varianten til ksh, men lad sammensatte kommandoer være for andre mislykkes det.
Svar
Følgende menes som en tilføjelse til @Patriks svar, hvis du ikke er i stand til at bruge en af de almindelige løsninger.
Dette svar antager følgende:
- Du har en skal, der ikke kender
$PIPESTATUS
ellerset -o pipefail
- Du vil bruge et rør til parallel udførelse, så ingen midlertidige filer.
- Du ønsker ikke at have mere rod, hvis du afbryder scriptet, muligvis ved en pludselig strømafbrydelse.
- Denne løsning skal være relativt let at følge og ren at læse.
- Du gør ikke ønsker at introducere yderligere subshells.
- Du kan ikke rode med de eksisterende filbeskrivere, så stdin / out / err må ikke berøres (hvordan altid kan du introducere noget nyt midlertidigt)
Yderligere antagelser. Du kan slippe af med alt, men dette klipper opskriften for meget, så den er ikke dækket her:
- Alt du vil vide er, at alle kommandoer i PIPE har exit-kode 0.
- Du har ikke brug for yderligere oplysninger om sidebånd.
- Din skal venter på, at alle rørkommandoer vender tilbage.
Før: foo | bar | baz
, men dette returnerer kun udgangskoden for den sidste kommando (baz
)
Ønsket: $?
må ikke være 0
(true), hvis nogen af kommandoerne i røret mislykkedes
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
Forklaret:
- En tempfile oprettes med
mktemp
. Dette opretter normalt straks en fil i/tmp
- Denne tempfile omdirigeres derefter til FD 9 for skrivning og FD 8 for læsning
- Derefter tempfile slettes straks. Det forbliver dog åbent, indtil begge FDer forsvinder.
- Nu er røret startet. Hvert trin føjes kun til FD 9, hvis der var en fejl.
-
wait
er nødvendig tilksh
, fordiksh
ellers ikke venter på, at alle rørkommandoer er færdige. Bemærk dog, at der er uønskede bivirkninger, hvis nogle baggrundsopgaver er til stede, så jeg kommenterede det som standard.Hvis ventetiden ikke gør ondt, kan du kommentere det. - Bagefter læses filens indhold. Hvis det er tomt (fordi alt fungerede)
read
returnererfalse
, såtrue
angiver en fejl
Dette kan bruges som erstatning for plugin til en enkelt kommando og behøver kun at følge:
- Ubrugte FDer 9 og 8
- En enkelt miljøvariabel, der indeholder navnet på tempfilen
- Og dette opskrift kan tilpasses stort set enhver shell derude, som tillader IO-omdirigering
- Det er også ret agnostisk platform og har ikke brug for ting som
/proc/fd/N
BUGs:
Dette script har en fejl, hvis /tmp
løber tør for plads. Hvis du også har brug for beskyttelse mod denne kunstige sag, du kan gøre det som følger, men dette har den ulempe, at antallet af 0
i 000
afhænger af antallet af kommandoer irør, så det er lidt mere kompliceret:
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"
Noter om bærbarhed:
-
ksh
og lignende skaller, der kun venter på den sidste rørkommando, har brug forwait
ukommenteret -
Det sidste eksempel bruger
printf "%1s" "$?"
i stedet forecho -n "$?"
fordi dette er mere bærbart. Ikke alle platforme fortolker-n
korrekt. -
printf "$?"
ville også gøre det, dogprintf "%1s"
fanger nogle hjørnesager, hvis du kører scriptet på en virkelig brudt platform. (Læs: hvis du tilfældigvis programmerer iparanoia_mode=extreme
.) -
FD 8 og FD 9 kan være højere på platforme, der understøtter flere cifre. AFAIR en POSIX-kompatibel shell behøver kun at understøtte enkelt cifre.
-
Blev testet med Debian 8.2
sh
,bash
,ksh
,ash
,sash
og enddacsh
Svar
Dette er bærbart, dvs. fungerer med en hvilken som helst POSIX-kompatibel shell, kræver ikke, at den aktuelle mappe kan skrives, og tillader, at flere scripts, der bruger det samme trick, kører samtidigt. her er en stærkere version efter Gilles “kommentarer:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: og her er en lidt lettere variant efter dubiousjim-kommentar:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
Kommentarer
- Dette fungerer ikke ‘ t af flere grunde. 1. Den midlertidige fil kan læses, før den ‘ skrives. 2. Oprettelse af en midlertidig fil i en delt mappe med et forudsigeligt navn er usikker (trivielt DoS, symlink race). 3. Hvis det samme script bruger dette trick flere gange, bruger det ‘ altid det samme filnavn. For at løse 1 skal du læse filen, når rørledningen er afsluttet. For at løse 2 og 3 skal du bruge en midlertidig fil med et tilfældigt genereret navn eller i et privat bibliotek.
- +1 Nå er $ {PIPESTATUS [0]} lettere, men den grundlæggende idé her fungerer, hvis man kender til de problemer, som Gilles nævner.
- Du kan gemme et par subshells:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Jeg er enig i, at det ‘ er lettere med Bash, men i nogle sammenhænge er det det værd at vide, hvordan man undgår Bash.
Svar
Med en smule forsigtighed skal dette fungere:
foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status)
Kommentarer
- Hvad med at rydde op som jlliagre? Don ‘ t efterlader du en fil, der hedder foo-status?
- @Johan: Hvis du foretrækker mit forslag, skal du ‘ t tøv med at stemme den op;) Ud over ikke at forlade en fil har den fordelen ved at tillade flere processer at køre dette samtidigt, og den aktuelle mappe behøver ikke at være skrivbar.
Svar
Følgende “if” -blok kører kun, hvis “command” lykkedes:
if command; then # ... fi
Specifikt kan du køre noget som dette:
haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi
Hvilket kører haconf -makerw
og gem stdout og stderr til “$ haconf_out”. Hvis den returnerede værdi fra haconf
er sand, vil “if” -blokken blive udført, og grep
vil læse “$ haconf_out” og prøve for at matche den mod “Cluster allerede skrivbar”.
Bemærk, at rør automatisk renser sig selv op; med omdirigeringen skal du være forsigtig med at fjerne “$ haconf_out”, når du er færdig.
Ikke så elegant som pipefail
, men et legitimt alternativ, hvis denne funktionalitet er ikke inden for rækkevidde.
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 mindst) kombineret med set -e
kan man bruge subshell til eksplicit at emulere pipefail og afslutte på rørfejl
set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program
Så hvis foo
mislykkes af en eller anden grund – vil resten af programmet ikke blive udført, og scriptet afsluttes med den tilsvarende fejlkode. (Dette forudsætter, at foo
udskriver sin egen fejl, hvilket er tilstrækkeligt til at forstå årsagen til fejl)
Svar
REDIGER : Dette svar er forkert, men interessant, så jeg vil lade det være i fremtiden reference.
Tilføjelse af en !
til kommandoen inverterer 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, det er ikke relateret. I dit eksempel vil jeg vide udgangskoden til
ls
, ikke invertere udgangskoden tilbogus_command
- Jeg foreslår at trække svaret tilbage.
- Nå ser det ud til, at jeg ‘ er en idiot. Jeg ‘ Vi har faktisk brugt dette i et script, før jeg troede, at det gjorde, hvad OP ønskede. God ting, jeg brugte det ikke ‘ til noget vigtigt