Få udgangsstatus for proces, der ' s røres til en anden

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

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

  1. 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.
  2. 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.
  3. 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.
  4. A rør oprettes, og kommandoerne til venstre (#part5 og #part6

) udføres. Outputtet frafilteromdirigeres til filbeskrivelse 4. I#part1blev filbeskrivelsen 4 omdirigeret til stdout. Dette betyder, at output affilterer stdout af hele konstruktionen.

  • Udgangsstatus fra #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 til someprog 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 via bash 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/ffgetopt (eller lignende) parsing ved hjælp af optarg, som er det næste element i ARGV, som argumentet for -o, så det mislykkes. Hvis du skulle lave en indpakning (sig, bash-pf, der bare gjorde exec /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. Brug mktemp , 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 dryssede wait 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 en if 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 eller set -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 til ksh, fordi ksh 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 returnerer false, 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 for wait ukommenteret

    • Det sidste eksempel bruger printf "%1s" "$?" i stedet for echo -n "$?" fordi dette er mere bærbart. Ikke alle platforme fortolker -n korrekt.

    • printf "$?" ville også gøre det, dog printf "%1s" fanger nogle hjørnesager, hvis du kører scriptet på en virkelig brudt platform. (Læs: hvis du tilfældigvis programmerer i paranoia_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 endda csh

    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 til bogus_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

    Skriv et svar

    Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *