Verkrijg de exitstatus van het proces dat ' s doorgesluisd naar een andere

Ik heb twee processen foo en bar, verbonden met een pijp:

$ foo | bar 

bar verlaat altijd 0; Ik “ben geïnteresseerd in de afsluitcode van foo. Is er een manier om erbij te komen?

Reacties

Answer

bash en zsh hebben een array-variabele dat de exitstatus bevat van elk element (commando) van de laatste pijplijn die door de shell is uitgevoerd.

Als je bash gebruikt, wordt de array PIPESTATUS (het geval is belangrijk!) en de array-indicaties beginnen bij nul:

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

Als u zsh, de array wordt pipestatus genoemd (het maakt niet uit!) en de array-indices beginnen bij één:

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

Om ze binnen een functie te combineren op een manier die “de waarden niet verliest:

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

Voer het bovenstaande uit in bash of zsh en je “krijgt dezelfde resultaten; slechts één van retval_bash en retval_zsh zal worden ingesteld. De andere is blanco. Hierdoor zou een functie kunnen eindigen met return $retval_bash $retval_zsh (let op het ontbreken van aanhalingstekens!).

Reacties

  • En pipestatus in zsh. Helaas hebben andere shells deze functie niet ‘.
  • Opmerking: arrays in zsh beginnen contra-intuïtief bij index 1, dus ‘ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Je zou de hele pijplijn als volgt kunnen controleren: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @JanHudec: Misschien moet je de eerste vijf woorden van mijn antwoord lezen. Geef ook aan waar de vraag om een POSIX-only antwoord vroeg.
  • @JanHudec: Noch werd het als POSIX getagd. Waarom neem je aan dat het antwoord POSIX moet zijn? Het was niet gespecificeerd, dus ik heb een bekwaam antwoord gegeven. Er is niets onjuist aan mijn antwoord, en er zijn meerdere antwoorden voor andere gevallen.

Antwoord

Daar zijn drie veelgebruikte manieren om dit te doen:

Pipefail

De eerste manier is om de optie pipefail (ksh, zsh of bash). Dit is de eenvoudigste en wat het doet, is in feite de exitstatus $? instellen op de exitcode van het laatste programma om niet-nul te verlaten (of nul als alles succesvol is afgesloten).

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

$ PIPESTATUS

Bash heeft ook een arrayvariabele genaamd $PIPESTATUS ($pipestatus in zsh) die de afsluitstatus bevat van alle programmas in de laatste pijplijn.

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

Je kunt het 3e commandovoorbeeld gebruiken om de specifieke waarde in de pijplijn te krijgen die je nodig hebt.

Afzonderlijke uitvoeringen

Dit is de meest logge van de oplossingen. Voer elk commando afzonderlijk uit en leg de status vast

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

Reacties

  • Verdorie! Ik wilde net iets over PIPESTATUS posten.
  • Ter referentie worden er verschillende andere technieken besproken in deze SO-vraag: stackoverflow.com/questions/1221833/…
  • @Patrick de pipestatus-oplossing werkt op bash, gewoon meer quastion voor het geval ik ksh-script gebruik, denk je dat we iets kunnen vinden dat lijkt op pipestatus? , (ondertussen zie ik dat de pipestatus niet wordt ondersteund door ksh)
  • @yael Ik gebruik ‘ niet ksh, maar in een korte blik erop ‘ s manpage, ‘ ondersteunt $PIPESTATUS of iets dergelijks. Het ondersteunt wel de pipefail optie.
  • Ik besloot om met pipefail te gaan, omdat het me hier de status van het mislukte commando laat zien: LOG=$(failed_command | successful_command)

Antwoord

Deze oplossing werkt zonder bash-specifieke functies of tijdelijke bestanden te gebruiken . Bonus: uiteindelijk is de exit-status eigenlijk een exit-status en niet een tekenreeks in een bestand.

Situatie:

someprog | filter 

jij wil de exit-status van someprog en de output van filter.

Hier is mijn oplossing:

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

het resultaat van deze constructie is stdout van filter als stdout van de constructie en sluit de status af van someprog als exitstatus van het construct.


deze constructie werkt ook met eenvoudige opdrachtgroepering {...} in plaats van subshells (...). subshells hebben enkele implicaties, waaronder prestatiekosten, die we hier niet nodig hebben. lees de fijne bash-handleiding voor meer details: 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 

Helaas vereist de bash-grammatica spaties en puntkommas voor de accolades zodat de constructie veel ruimer wordt.

Voor de rest van deze tekst zal ik de subshell-variant gebruiken.


Voorbeeld someprog en 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 $? 

Voorbeelduitvoer:

filtered line1 filtered line2 filtered line3 42 

Opmerking: het onderliggende proces erft de open bestandsdescriptors van de ouder. Dat betekent dat someprog de open bestandsdescriptor 3 en 4 zal erven. Als someprog naar bestandsdescriptor 3 schrijft, wordt dat de exitstatus. De echte exit-status wordt genegeerd omdat read maar één keer leest.

Als u zich zorgen maakt dat uw someprog zou kunnen schrijven naar bestandsdescriptor 3 of 4, dan is het het beste om de bestandsdescriptors te sluiten voordat u someprog aanroept.

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

De exec 3>&- 4>&- voor someprog sluit de bestandsbeschrijving voordat someprog wordt uitgevoerd, dus voor someprog die bestandsdescriptors bestaan gewoon niet.

Het kan ook als volgt worden geschreven: someprog 3>&- 4>&-


Stap voor stap uitleg van de constructie:

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

Van onder naar boven:

  1. Er wordt een subshell gemaakt met bestandsdescriptor 4 omgeleid naar stdout. Dit betekent dat alles wat naar bestandsdescriptor 4 in de subshell wordt afgedrukt, eindigt als de stdout van de hele constructie.
  2. Er wordt een pipe gemaakt en de opdrachten aan de linkerkant (#part3) en right (#part2) worden uitgevoerd. exit $xs is ook het laatste commando van de pipe en dat betekent dat de string van stdin de exitstatus van de hele constructie zal zijn.
  3. Een subshell wordt gemaakt met file descriptor 3 doorgestuurd naar stdout. Dit betekent dat alles wat in deze subshell naar bestandsdescriptor 3 wordt afgedrukt, in #part2 terechtkomt en op zijn beurt de exitstatus van de hele constructie is.
  4. A pipe wordt gemaakt en de opdrachten aan de linkerkant (#part5 en #part6) en rechts (filter >&4) worden uitgevoerd. De uitvoer van filter wordt omgeleid naar bestandsdescriptor 4. In #part1 werd de bestandsdescriptor 4 omgeleid naar stdout. Dit betekent dat de uitvoer van filter de stdout is van de hele constructie.
  5. Exit-status van #part6 wordt afgedrukt naar bestandsdescriptor 3. In #part3 werd bestandsdescriptor 3 omgeleid naar #part2. Dit betekent dat de exit-status van #part6 de laatste exit-status voor de hele constructie is.
  6. someprog is uitgevoerd. De exitstatus wordt genomen in #part5. De stdout wordt ingenomen door de pipe in #part4 en doorgestuurd naar filter. De uitvoer van filter zal op zijn beurt stdout bereiken zoals uitgelegd in #part4

Reacties

  • Leuk. Voor de functie zou ik (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) vereenvoudigen tot someprog 3>&- 4>&-.
  • Deze methode werkt ook zonder subshells: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Antwoord

Hoewel het niet precies is wat je vroeg, zou je

#!/bin/bash -o pipefail 

kunnen gebruiken zodat je pipes de laatste niet-nul return retourneren.

is misschien wat minder coderend

Bewerken: voorbeeld

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

Opmerkingen

  • set -o pipefail binnen het script zou robuuster moeten zijn, bijv. voor het geval iemand het script uitvoert via bash foo.sh.
  • Hoe werkt dat? heb je een voorbeeld?
  • Merk op dat -o pipefail niet in POSIX staat.
  • Dit werkt niet in mijn BASH 3.2.25 ( 1) -release. Bovenaan / tmp / ff heb ik #!/bin/bash -o pipefail.Fout is: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: Sommige omgevingen (inclusief linux) doneren ‘ t ontleden spaties op #! regels voorbij de eerste, en dus wordt dit /bin/bash -o pipefail /tmp/ff, in plaats van de benodigde /bin/bash -o pipefail /tmp/ffgetopt (of vergelijkbaar) parseren met de optarg, het volgende item in ARGV, als argument voor -o, dus het mislukt. Als u een wrapper zou maken (zeg, bash-pf die zojuist exec /bin/bash -o pipefail "$@" heeft gedaan, en die op de #! regel, dat zou werken. Zie ook: en.wikipedia.org/wiki/Shebang_%28Unix%29

Antwoord

Wat ik doe wanneer mogelijk is de afsluitcode van foo naar bar. Als ik bijvoorbeeld weet dat foo nooit een regel met alleen cijfers produceert, kan ik gewoon de exitcode gebruiken:

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

Of als ik weet dat de uitvoer van foo nooit een regel bevat met alleen .:

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

Dit kan altijd gedaan worden als er een manier is om bar om op alle regels behalve de laatste regel te werken, en de laatste regel door te geven als de exitcode.

Als bar een complexe pijplijn is waarvan u de output niet nodig, je kunt een deel ervan overslaan door de afsluitcode op een andere bestandsdescriptor af te drukken.

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

Hierna $exit_codes is meestal foo:X bar:Y, maar het kan bar:Y foo:X zijn als bar stopt voordat u al zijn invoer leest of als u pech heeft. Ik denk dat schrijfacties naar leidingen van maximaal 512 bytes atomair zijn op alle unices, dus de delen foo:$? en bar:$? worden niet met elkaar vermengd als zolang de tag-strings kleiner zijn dan 507 bytes.

Als je de uitvoer van bar wilt vastleggen, wordt het moeilijk. Je kunt de bovenstaande technieken combineren door want de uitvoer van bar bevat nooit een regel die eruitziet als een exitcode-indicatie, maar het wordt wel onhandig.

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") 

En natuurlijk is er de eenvoudige optie om een tijdelijk bestand te gebruiken om de status op te slaan. Eenvoudig, maar niet zo eenvoudig in productie:

  • Als er meerdere scripts gelijktijdig worden uitgevoerd, of als hetzelfde script deze methode op verschillende plaatsen gebruikt, moet u ervoor zorgen dat zorg ervoor dat ze verschillende tijdelijke bestandsnamen gebruiken.
  • Het veilig maken van een tijdelijk bestand in een gedeelde map is moeilijk. Vaak is /tmp de enige plaats waar een script zeker in staat is om bestanden te schrijven. Gebruik mktemp , dat niet POSIX is, maar tegenwoordig beschikbaar is op alle serieuze unices.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Reacties

  • Bij gebruik van de tijdelijke bestandsbenadering voeg ik liever een trap toe voor EXIT die alle tijdelijke bestanden verwijdert zodat er geen afval achterblijft, zelfs als het script sterft

Answer

Beginnend met de pijplijn:

foo | bar | baz 

Hier is een algemene oplossing die alleen POSIX-shell gebruikt en geen tijdelijke bestanden:

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

$error_statuses bevat de statuscodes van mislukte processen, in willekeurige volgorde, met indexen om aan te geven welk commando elke status heeft verzonden.

# 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 

Let op de aanhalingstekens rond $error_statuses in mijn tests; zonder hen kan grep “niet differentiëren omdat de nieuwe regels naar spaties worden gedwongen.

Antwoord

Als je het moreutils -pakket hebt geïnstalleerd, kun je het mispipe -hulpprogramma gebruiken dat precies doet wat je gevraagd hebt.

Antwoord

Dus ik wilde een antwoord bijdragen zoals lesmanas, maar ik denk dat het mijne misschien een beetje eenvoudiger en iets voordeliger is puur- Bourne-shell-oplossing:

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

Ik denk dat dit het beste van binnenuit kan worden uitgelegd – command1 zal zijn normale uitvoer uitvoeren en afdrukken op stdout (bestandsdescriptor 1) , als het klaar is, zal printf de afsluitcode van commando1 uitvoeren en afdrukken op zijn stdout, maar die stdout wordt omgeleid naar bestandsdescriptor 3.

Terwijl command1 wordt uitgevoerd, wordt zijn stdout doorgestuurd naar command2 (de uitvoer van printf haalt nooit command2 omdat we het naar bestandsdescriptor 3 sturen in plaats van 1, wat ik s wat de pijp leest).Vervolgens sturen we de uitvoer van command2 door naar bestandsdescriptor 4, zodat het ook buiten bestandsdescriptor 1 blijft – omdat we bestandsdescriptor 1 een klein beetje later vrij willen, omdat we de printf-uitvoer op bestandsdescriptor 3 weer terugbrengen naar bestandsdescriptor 1 – omdat dat is wat de opdrachtsubstitutie (de backticks) zal vangen en dat wordt in de variabele geplaatst.

Het laatste stukje magie is dat eerste exec 4>&1 deden we als een apart commando – het opent bestandsdescriptor 4 als een kopie van de stdout van de externe shell. Commandosubstitutie zal alles wat op standaard is geschreven vastleggen vanuit het perspectief van de commandos erin – maar aangezien de uitvoer van command2 naar bestandsdescriptor 4 gaat voor zover het de commandosubstitutie betreft, legt de commandosubstitutie het niet vast – echter, zodra het “uit” de opdrachtsubstitutie komt, gaat het in feite nog steeds naar de algemene bestandsbeschrijver 1 van het script.

(De exec 4>&1 heeft om een afzonderlijk commando te zijn, omdat veel gewone shells het niet leuk vinden als je probeert te schrijven naar een bestandsdescriptor binnen een opdrachtvervanging, die wordt geopend in het externe commando dat de vervanging gebruikt. Dit is dus de eenvoudigste draagbare manier om het te doen.)

Je kunt er op een minder technische en meer speelse manier naar kijken, alsof de output van de commandos elkaar overhaast: command1 leidt naar command2, dan springt de output van printf over commando 2 zodat commando2 het niet opvangt, en dan springt de uitvoer van commando 2 over en uit de De opdrachtvervanging net zoals printf net op tijd landt om door de vervanging gevangen te worden, zodat het in de variabele terechtkomt, en de uitvoer van command2 gaat op zijn vrolijke manier naar de standaarduitvoer, net als in een normale pipe. / p>

Ook, zoals ik het begrijp, zal $? nog steeds de retourcode van het tweede commando in de pipe bevatten, omdat variabeletoewijzingen, commando-vervangingen en samengestelde commandos zijn allemaal effectief transparant voor de retourcode van het commando erin, dus de retourstatus van command2 zou moeten worden gepropageerd – dit, en omdat ik geen extra functie hoef te definiëren, is waarom ik denk dat dit een iets betere oplossing is dan de voorgestelde oplossing door lesmana.

Volgens de voorbehouden die lesmana noemt, is het mogelijk dat commando1 op een gegeven moment bestandsdescriptors 3 of 4 gebruikt, dus om robuuster te zijn, zou je doen:

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

Merk op dat ik samengestelde commandos gebruik in mijn voorbeeld, maar subshells (gebruik ( ) in plaats van { } zal ook werken, hoewel het misschien minder efficiënt is.)

Commandos nemen bestandsdescriptors over van het proces dat start ze, dus de hele tweede regel zal bestandsdescriptor vier erven, en het samengestelde commando gevolgd door 3>&1 zal bestandsdescriptor drie erven. Dus de 4>&- zorgt ervoor dat de binnenste samengestelde opdracht bestandsdescriptor vier niet erven, en de 3>&- zal bestandsdescriptor drie niet erven, dus command1 krijgt een “schonere”, meer standaardomgeving. Je zou ook de binnenste 4>&- naast de 3>&- kunnen plaatsen, maar ik bedenk waarom je de reikwijdte niet zo veel mogelijk beperkt.

Ik weet niet zeker hoe vaak dingen bestandsdescriptor drie en vier rechtstreeks gebruiken – ik denk dat de meeste tijdprogrammas syscalls gebruiken die op dat moment niet-gebruikte bestandsdescriptors retourneren, maar soms schrijft code naar een bestand descriptor 3 rechtstreeks, denk ik (ik kan me voorstellen dat een programma een bestandsdescriptor controleert om te zien of het open is, en het gebruikt als het zo is, of dat het zich anders gedraagt als dat niet het geval is). Dus het laatste is waarschijnlijk het beste om te behouden in gedachten en gebruik voor algemene gevallen.

Reacties

  • Ziet er interessant uit, maar ik kan ‘ Ik weet niet goed wat je verwacht dat deze opdracht doet, en mijn computer kan het ook ‘ t; ik krijg -bash: 3: Bad file descriptor.
  • @ G-Man Juist, ik vergeet steeds dat bash geen idee heeft wat het ‘ doet als het mes naar bestandsdescriptors, in tegenstelling tot de shells die ik normaal gebruik (de as die bij busybox wordt geleverd). Ik ‘ laat het je weten als ik een oplossing bedenk die bash blij maakt. Als u in de tussentijd ‘ een Debian-box bij de hand hebt, kunt u deze in een streepje proberen, of als u ‘ busybox bij de hand hebt kan het proberen met de busybox ash / sh.
  • @ G-Man Wat betreft wat ik verwacht dat het commando doet, en wat het doet in andere shells, is omleiden stdout van command1 zodat het niet ‘ wordt niet betrapt door de opdrachtvervanging, maar eenmaal buiten de opdrachtvervanging, gaat het terug naar stdout, dus het ‘ wordt doorgesluisd zoals verwacht naar command2.Wanneer commando1 wordt afgesloten, wordt de printf geactiveerd en wordt de uitgangsstatus afgedrukt, die in de variabele wordt vastgelegd door de opdrachtsubstitutie. Zeer gedetailleerde uitsplitsing hier: stackoverflow.com/questions/985876/tee-and-exit-status/… Ook , dat commentaar van jou gelezen alsof het een beetje beledigend bedoeld was?
  • Waar zal ik beginnen? (1) Het spijt me als u zich beledigd voelde. “Ziet er interessant uit” werd serieus bedoeld; het zou geweldig zijn als zoiets compacts zo goed zou werken als je had verwacht. Afgezien daarvan zei ik eenvoudigweg dat ik niet begreep wat uw oplossing moest doen. Ik werk / speel al lange tijd met Unix (sinds voordat Linux bestond), en, als ik iets niet begrijp, is dat een rode vlag die misschien andere mensen zullen het ook niet begrijpen, en dat het meer uitleg nodig heeft (IMNSHO). … (Vervolg)
  • (vervolg)… Sinds jij “… graag denkt… dat [jij] zo ongeveer alles meer dan de gemiddelde persoon ”, misschien moet u onthouden dat het doel van Stack Exchange niet is om een dienst voor het schrijven van commandos te zijn, met duizenden eenmalige oplossingen voor triviaal verschillende vragen; maar veeleer om mensen te leren hoe ze hun eigen problemen kunnen oplossen. En voor dat doel moet je de dingen misschien goed genoeg uitleggen zodat een “gemiddeld persoon” het kan begrijpen. Kijk naar het antwoord van lesmana voor een voorbeeld van een uitstekende uitleg. … (Vervolg)

Answer

lesmanas oplossing hierboven kan ook worden gedaan zonder de overhead van het starten van geneste subprocessen door in plaats daarvan { .. } te gebruiken (onthoud dat deze vorm van gegroepeerde opdrachten altijd moet eindigen met puntkommas). Iets als dit:

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

Ik “heb deze constructie gecontroleerd met dash-versie 0.5.5 en bash-versie 3.2.25 en 4.2.42, dus zelfs als sommige shells { .. } groepering, is het nog steeds POSIX-compatibel.

Reacties

  • Dit werkt echt goed met de meeste shells die ik ‘ heb het geprobeerd, inclusief NetBSD sh, pdksh, mksh, dash, bash. Maar ik kan ‘ het niet laten werken met AT & T Ksh (93s +, 93u +) of zsh (4.3.9, 5.2), zelfs met set -o pipefail in ksh of een willekeurig aantal gesprenkelde wait commandos in een van beide. Ik denk dat het gedeeltelijk kan wees in ieder geval een parseerprobleem voor ksh, alsof ik bij het gebruik van subshells blijf, dan werkt het prima, maar zelfs met een if om de subshell-variant voor ksh te kiezen, maar laat de samengestelde opdrachten staan voor anderen mislukt het.

Answer

Het volgende is bedoeld als een add-on voor het antwoord van @Patrik, voor het geval je niet in staat bent om een van de gebruikelijke oplossingen te gebruiken.

Dit antwoord veronderstelt het volgende:

  • Je hebt een shell die noch set -o pipefail
  • U wilt een pipe gebruiken voor parallelle uitvoering, dus geen tijdelijke bestanden.
  • U geen extra rommel willen hebben als u het script onderbreekt, mogelijk door een plotselinge stroomuitval.
  • Deze oplossing moet relatief eenvoudig te volgen en schoon te lezen zijn.
  • Dat doet u geen extra subshells wilt introduceren.
  • Je kunt niet met de bestaande bestandsdescriptors spelen, dus stdin / out / err mag niet worden aangeraakt (hoe ooit kun je tijdelijk een nieuwe introduceren)

Aanvullende veronderstellingen. Je kunt ze allemaal kwijt, maar dit verstoort het recept te veel, dus het wordt hier niet behandeld:

  • Het enige dat je wilt weten is dat alle commandos in de PIPE exitcode 0 hebben.
  • Je hebt geen aanvullende zijbandinformatie nodig.
  • Je shell wacht tot alle pipe-commandos zijn teruggekeerd.

Voor: foo | bar | baz, maar dit geeft alleen de afsluitcode van het laatste commando terug (baz)

Gezocht: $? mag niet 0 (true) zijn, als een van de commandos in de pipe is mislukt

Na:

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 

Uitgelegd:

  • Er wordt een tijdelijk bestand gemaakt met mktemp. Dit creëert gewoonlijk onmiddellijk een bestand in /tmp
  • Dit tijdelijke bestand wordt dan omgeleid naar FD 9 voor schrijven en FD 8 voor lezen
  • tempfile wordt onmiddellijk verwijderd. Het blijft echter open totdat beide FDs ophouden.
  • Nu wordt de pipe gestart. Elke stap wordt alleen toegevoegd aan FD 9 als er een fout is opgetreden.
  • De wait is nodig voor ksh, omdat ksh anders niet wacht tot alle pipe-opdrachten zijn voltooid. Houd er echter rekening mee dat er ongewenste neveneffecten zijn als sommige achtergrondtaken aanwezig zijn, dus heb ik er standaard commentaar op gegeven.Als het wachten geen pijn doet, kun je er commentaar op geven.
  • Daarna wordt de inhoud van het bestand gelezen. Als het leeg is (omdat alles werkte) read geeft false terug, dus true geeft een fout aan

Dit kan worden gebruikt als vervanging van een plug-in voor een enkele opdracht en heeft alleen het volgende nodig:

  • Ongebruikte FDs 9 en 8
  • Een enkele omgevingsvariabele voor de naam van het tijdelijke bestand
  • En dit recept kan worden aangepast aan vrijwel elke shell die IO-omleiding mogelijk maakt
  • Het is ook redelijk platformonafhankelijk en heeft geen dingen nodig als /proc/fd/N

BUGs:

Dit script bevat een bug voor het geval /tmp geen ruimte meer heeft. Als je ook bescherming nodig hebt tegen dit kunstmatige geval, je kunt het als volgt doen, maar dit heeft het nadeel dat het aantal 0 in 000 afhangt van het aantal commandos in depipe, dus het is iets gecompliceerder:

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" 

Opmerkingen over draagbaarheid:

  • ksh en soortgelijke shells die alleen wachten op het laatste pipe-commando hebben de wait ongecommentarieerd

  • Het laatste voorbeeld gebruikt printf "%1s" "$?" in plaats van echo -n "$?" omdat dit draagbaarder is. Niet elk platform interpreteert -n correct.

  • printf "$?" zou het ook doen, printf "%1s" vangt echter enkele hoekgevallen op voor het geval je het script op een echt kapot platform draait. (Lees: als je toevallig programmeert in paranoia_mode=extreme.)

  • FD 8 en FD 9 kunnen hoger zijn op platforms die meerdere cijfers. AFAIR een POSIX-conforme shell hoeft slechts enkele cijfers te ondersteunen.

  • Is getest met Debian 8.2 sh, bash, ksh, ash, sash en zelfs csh

Antwoord

Dit is draagbaar, dwz werkt met elke POSIX-compatibele shell, “vereist niet dat de huidige directory schrijfbaar is en staat toe dat meerdere scripts die dezelfde truc gebruiken tegelijkertijd worden uitgevoerd.

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

Bewerken: hier is een sterkere versie na Gilles “commentaren:

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

Bewerken2: en hier is een iets lichtere variant na dubiousjim commentaar:

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

Reacties

  • Dit werkt niet ‘ om verschillende redenen. 1. Het tijdelijke bestand mag worden gelezen voordat het ‘ wordt geschreven. 2. Het aanmaken van een tijdelijk bestand in een gedeelde map met een voorspelbare naam is onveilig (triviale DoS, symlink race). 3. Als hetzelfde script deze truc meerdere keren gebruikt, gebruikt het ‘ altijd dezelfde bestandsnaam. Om 1 op te lossen, leest u het bestand nadat de pijplijn is voltooid. Om 2 en 3 op te lossen, gebruikt u een tijdelijk bestand met een willekeurig gegenereerde naam of in een privé-directory.
  • +1 Welnu, de $ {PIPESTATUS [0]} is eenvoudiger, maar het basisidee hier werkt als men weet van de problemen die Gilles noemt.
  • Je kunt een paar subshells opslaan: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Ik ben het ermee eens dat ‘ gemakkelijker is met Bash, maar in sommige contexten is het de moeite waard om te weten hoe je Bash kunt vermijden.

Antwoord

Met een beetje voorzorg zou dit moeten werken:

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

Opmerkingen

  • Hoe zit het met opruimen zoals jlliagre? Laat ‘ je geen bestand achter met de naam foo-status?
  • @Johan: Als je mijn suggestie prefereert, don ‘ aarzel niet om het te stemmen;) Naast het niet verlaten van een bestand, heeft het het voordeel dat meerdere processen dit tegelijkertijd kunnen uitvoeren en de huidige directory niet beschrijfbaar hoeft te zijn.

Answer

Het volgende “if” -blok wordt alleen uitgevoerd als “command” is geslaagd:

if command; then # ... fi 

Specifiek gesproken kunt u zoiets als dit uitvoeren:

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

Wat wordt uitgevoerd haconf -makerw en sla zijn stdout en stderr op in “$ haconf_out”. Als de geretourneerde waarde van haconf waar is, dan zal het “if” blok worden uitgevoerd en grep zal “$ haconf_out” lezen. om het te vergelijken met “Cluster is al beschrijfbaar”.

Merk op dat leidingen zichzelf automatisch opruimen; met de omleiding moet je “voorzichtig zijn om” $ haconf_out “te verwijderen als je klaar bent.

Niet zo elegant als pipefail, maar een legitiem alternatief als deze functionaliteit is niet binnen handbereik.

Antwoord

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 $ 

Antwoord

(Met tenminste bash) gecombineerd met set -e kan men subshell gebruiken om expliciet pipefail te emuleren en exit on pipe error

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

Dus als foo om de een of andere reden mislukt, wordt de rest van het programma niet uitgevoerd en wordt het script afgesloten met de bijbehorende foutcode. (Dit veronderstelt dat foo zijn eigen fout afdrukt, wat voldoende is om de reden van de fout te begrijpen)

Antwoord

EDIT : dit antwoord is fout, maar interessant, dus ik laat het voor de toekomst referentie.


Door een ! aan het commando toe te voegen, wordt de retourcode omgekeerd.

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

Reacties

  • Ik denk dat dit geen verband houdt. In uw voorbeeld wil ik de exitcode van ls weten, niet de exitcode van bogus_command
  • Ik stel voor dat antwoord terug te trekken.
  • Nou, het lijkt erop dat ik ‘ een idioot ben. Ik ‘ heb dit daadwerkelijk in een script gebruikt voordat ik dacht dat het deed wat het OP wilde. Maar goed dat ik ‘ het niet voor iets belangrijks heb gebruikt

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *