Obțineți starea de ieșire a procesului care ' este canalizat către un alt

Am două procese foo și bar, conectat cu o conductă:

$ foo | bar 

bar iese întotdeauna de la 0; „Mă interesează codul de ieșire al foo. Există vreo modalitate de a ajunge la el?

Comentarii

Răspuns

bash și zsh au o variabilă matrice care deține starea de ieșire a fiecărui element (comandă) din ultima conductă executată de shell.

Dacă utilizați bash, tabloul este numit PIPESTATUS (cazul contează!) și indicii matrice încep de la zero:

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

Dacă utilizați zsh, matricea se numește pipestatus (cazul contează!), iar indicii matricei încep de la unul:

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

Pentru a le combina într-o funcție într-un mod care nu „pierde valorile:

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

Rulați cele de mai sus în bash sau zsh și veți obține aceleași rezultate; numai unul dintre retval_bash și retval_zsh va fi setat. Celălalt va fi gol. Acest lucru ar permite unei funcții să se încheie cu return $retval_bash $retval_zsh (rețineți lipsa ghilimelelor!).

Comentarii

  • Și pipestatus în zsh. Din păcate, alte shell-uri nu ‘ nu au această caracteristică.
  • Notă: tablourile din zsh încep contraintuitiv la indexul 1, deci ‘ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Puteți verifica întreaga conductă astfel: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @ JanHudec: Poate că ar trebui să citiți primele cinci cuvinte din răspunsul meu. De asemenea, indicați cu amabilitate în cazul în care întrebarea a solicitat un răspuns numai POSIX.
  • @JanHudec: Nici nu a fost etichetat POSIX. De ce presupuneți că răspunsul trebuie să fie POSIX? Nu a fost specificat, așa că am oferit un răspuns calificat. Nu există nimic incorect în răspunsul meu, plus că există mai multe răspunsuri pentru a aborda alte cazuri.

Răspuns

Acolo sunt 3 moduri obișnuite de a face acest lucru:

Pipefail

Prima modalitate este să setați opțiunea pipefail (ksh, zsh sau bash). Acesta este cel mai simplu și ceea ce face este să stabilească practic starea de ieșire $? la codul de ieșire al ultimului program pentru a ieși diferit de zero (sau zero dacă toate au ieșit cu succes). p>

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

$ PIPESTATUS

Bash are, de asemenea, o variabilă matrice numită $PIPESTATUS ($pipestatus în zsh) care conține starea de ieșire a tuturor programelor din ultima conductă.

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

Puteți utiliza cel de-al treilea exemplu de comandă pentru a obține valoarea specifică din conductă de care aveți nevoie.

Execuții separate

Aceasta este cea mai dificilă soluție. Rulați fiecare comandă separat și capturați starea

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

Comentarii

  • Darn! Pur și simplu aveam să postez despre PIPESTATUS.
  • Pentru referință, există alte câteva tehnici discutate în această întrebare SO: stackoverflow.com/questions/1221833/…
  • @Patrick soluția pipestatus funcționează pe bash, doar mai multă întrebare în cazul în care folosesc script ksh crezi că putem găsi ceva similar cu pipestatus? , (între timp văd pipestatusul nu este acceptat de ksh)
  • @yael nu ‘ nu folosesc ksh, dar dintr-o scurtă privire la pagina de manual ‘, nu ‘ nu acceptă $PIPESTATUS sau ceva similar. Totuși, acceptă opțiunea pipefail.
  • Am decis să merg cu pipefail, deoarece îmi permite să obțin starea comenzii eșuate aici: LOG=$(failed_command | successful_command)

Răspuns

Această soluție funcționează fără a utiliza caracteristici specifice bash sau fișiere temporare . Bonus: la final starea de ieșire este de fapt o stare de ieșire și nu un șir într-un fișier.

Situație:

someprog | filter 

doresc starea de ieșire din someprog și ieșirea din filter.

Iată soluția mea:

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

rezultatul acestei construcții este stdout din filter ca stdout al construcției și starea de ieșire din someprog ca stare de ieșire a constructului.


acest construct funcționează și cu gruparea simplă de comenzi {...} în loc de sub-shell-uri (...). sub-coajele au unele implicații, printre altele un cost de performanță, de care nu avem nevoie aici. citiți manualul bash fin pentru mai multe detalii: 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 

Din păcate, gramatica bash necesită spații și punct și virgulă pentru acoladele, astfel încât construcția să devină mult mai spațioasă.

Pentru restul acestui text voi folosi varianta sub-shell.


Exemplu someprog și 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 $? 

Exemplu de ieșire:

filtered line1 filtered line2 filtered line3 42 

Notă: procesul copil moștenește descriptorii fișierului deschis de la părinte. Asta înseamnă că someprog va moșteni descriptorul de fișier deschis 3 și 4. Dacă someprog scrie în descriptorul de fișier 3, atunci acesta va deveni starea de ieșire. Starea reală de ieșire va fi ignorată deoarece read citește o singură dată.

Dacă vă faceți griji că someprog ar putea scrie pentru descriptorul de fișiere 3 sau 4, atunci este mai bine să închideți descriptorii de fișiere înainte de a apela someprog.

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

exec 3>&- 4>&- înainte ca someprog să închidă descriptorul fișierului înainte de a executa someprog deci pentru someprog acești descriptori de fișiere pur și simplu nu există.

Poate fi scris și astfel: someprog 3>&- 4>&-


Explicație pas cu pas a construcției:

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

De jos în sus:

  1. Se creează un sub-shell cu descriptorul de fișier 4 redirecționat către stdout. Aceasta înseamnă că orice este imprimat în descriptorul de fișier 4 din subshell va ajunge ca stdout al întregului construct.
  2. Este creată o conductă și comenzile din stânga (#part3) și dreapta (#part2) sunt executate. exit $xs este, de asemenea, ultima comandă a conductei și asta înseamnă că șirul de la stdin va fi starea de ieșire a întregului construct.
  3. Se creează un subshell cu fișierul descriptorul 3 redirecționat către stdout. Aceasta înseamnă că tot ceea ce este tipărit în descriptorul de fișier 3 din acest subshell va ajunge în #part2 și, la rândul său, va fi starea de ieșire a întregului construct.
  4. A se creează conducta și comenzile din stânga (#part5 și #part6) și dreapta (filter >&4) sunt executate. Ieșirea filter este redirecționată către descriptorul de fișier 4. În #part1 descriptorul de fișier 4 a fost redirecționat către stdout. Acest lucru înseamnă că ieșirea filter este standardul întregului construct.
  5. Starea ieșirii din #part6 este tipărită la descriptorul de fișier 3. În #part3 descriptorul de fișier 3 a fost redirecționat către #part2. Aceasta înseamnă că starea de ieșire din #part6 va fi starea finală de ieșire pentru întregul construct.
  6. someprog este executat. Starea de ieșire este luată în #part5. Stdout-ul este preluat de conductă în #part4 și redirecționat către filter. Ieșirea din filter va ajunge la rândul său la stdout așa cum se explică în #part4

Comentarii

  • Frumos. Pentru funcția pe care o pot face (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) simplifică la someprog 3>&- 4>&-.
  • Această metodă funcționează și fără sub-cochilii: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Răspuns

Deși nu este exact ceea ce ați cerut, puteți utiliza

#!/bin/bash -o pipefail 

astfel încât țevile dvs. să returneze ultimul întoarcere non zero.

ar putea fi un pic mai puțin codificat

Edit: Exemplu

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

Comentarii

  • set -o pipefail din script ar trebui să fie mai robust, de ex. în cazul în care cineva execută scriptul prin bash foo.sh.
  • Cum funcționează? aveți un exemplu?
  • Rețineți că -o pipefail nu este în POSIX.
  • Acest lucru nu funcționează în BASH 3.2.25 ( 1) -eliberare. În partea de sus a / tmp / ff am #!/bin/bash -o pipefail.Eroarea este: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: Unele medii (inclusiv linux) nu ‘ nu analizează spații pe #! linii dincolo de prima și astfel aceasta devine /bin/bash -o pipefail /tmp/ff, în loc de /bin/bash -o pipefail /tmp/ffgetopt (sau similar) analizând folosind optarg, care este următorul articol din ARGV, ca argument pentru -o, deci nu reușește. Dacă ar fi să faci un wrapper (să zicem, bash-pf care tocmai a făcut exec /bin/bash -o pipefail "$@" și să-l pui pe #! linie, care ar funcționa. A se vedea, de asemenea: en.wikipedia.org/wiki/Shebang_%28Unix%29

Răspuns

Ceea ce fac când este posibil este să introduc codul de ieșire din foo în bar. De exemplu, dacă știu că foo nu produce niciodată o linie cu doar cifre, atunci pot purta codul de ieșire:

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

Sau dacă știu că ieșirea din foo nu conține niciodată o linie cu doar .:

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

Acest lucru se poate face întotdeauna dacă există un mod de a obține bar să funcționeze pe toate, cu excepția ultimei linii, și să treacă ultima linie drept cod de ieșire.

Dacă bar este o conductă complexă a cărei nu aveți nevoie, puteți ocoli o parte din acesta imprimând codul de ieșire pe un alt descriptor de fișier.

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

După aceasta $exit_codes este de obicei foo:X bar:Y, dar ar putea fi bar:Y foo:X dacă bar renunță înainte de a citi toate informațiile sale sau dacă nu aveți ghinion. Cred că scrierile către țevi de până la 512 octeți sunt atomice pe toate unitățile, așa că foo:$? și bar:$? părțile câștigate nu pot fi amestecate ca atâta timp cât șirurile de etichete sunt sub 507 octeți.

Dacă trebuie să capturați ieșirea din bar, devine dificil. Puteți combina tehnicile de mai sus aranjând pentru ieșirea bar să nu conțină niciodată o linie care să arate ca o indicație de cod de ieșire, dar devine dificilă.

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

Și, desigur, există opțiunea simplă folosind un fișier temporar pentru a stoca starea. Simplu, dar nu atât simplu în producție:

  • Dacă există mai multe scripturi care rulează simultan sau dacă același script folosește această metodă în mai multe locuri, trebuie să faceți sigur că utilizează diferite nume de fișiere temporare.
  • Crearea unui fișier temporar în siguranță într-un director partajat este dificilă. Adesea, /tmp este singurul loc în care un script este sigur că poate scrie fișiere. Utilizați mktemp , care nu este POSIX, dar este disponibil în toate unitățile serioase din zilele noastre.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Comentarii

  • Când folosesc abordarea temporară a fișierului, prefer să adaug o capcană pentru EXIT care elimină toate fișierele temporare astfel încât să nu rămână gunoi, chiar dacă scriptul moare

Răspuns

Începând de la conductă:

foo | bar | baz 

Iată o soluție generală care utilizează numai shell POSIX și nu are fișiere temporare:

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

$error_statuses conține codurile de stare ale oricăror procese eșuate, în ordine aleatorie, cu indexuri pentru a indica ce comandă a emis fiecare stare.

# 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 

Rețineți ghilimelele din jurul $error_statuses în testele mele; fără ele grep nu se poate diferenția deoarece liniile noi sunt constrânse la spații.

Răspuns

Dacă aveți instalat pachetul moreutils , puteți utiliza utilitarul mispipe care face exact ceea ce ați cerut.

Răspuns

Așa că am vrut să contribui cu un răspuns ca lesmana, dar cred că al meu este poate un pic mai simplu și puțin mai avantajos pur- Soluție Bourne-shell:

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

Cred că acest lucru este cel mai bine explicat din interior spre exterior – comanda1 va executa și imprima ieșirea sa obișnuită pe stdout (descriptorul de fișier 1) , apoi, odată ce a terminat, printf va executa și va imprima codul de ieșire al comenzii1 pe stdout-ul său, dar acel stdout este redirecționat către descriptorul de fișier 3.

În timp ce comanda1 se execută, stdout-ul său este canalizat către comanda2 (ieșirea printf nu o face niciodată la comanda2 pentru că o trimitem la descriptorul de fișier 3 în loc de 1, pe care i ceea ce citește conducta).Apoi redirecționăm ieșirea comenzii2 către descriptorul de fișiere 4, astfel încât să rămână în afara descriptorului de fișiere 1 – pentru că dorim descriptorul de fișiere 1 gratuit pentru un pic mai târziu, pentru că vom readuce printf-ul de pe descriptorul de fișiere 3 înapoi în descriptorul de fișiere 1 – pentru că așa va captura substituția comenzii (backticks-urile) și asta va fi plasat în variabilă.

Bitul final de magie este că primul exec 4>&1 am făcut-o ca o comandă separată – deschide descriptorul de fișier 4 ca o copie a stdout-ului shell-ului extern. Înlocuirea comenzii va captura orice este scris în standard din perspectiva comenzilor din interior – dar, din moment ce ieșirea comenzii2 va fi descriptorul de fișier 4 în ceea ce privește înlocuirea comenzii, înlocuirea comenzii nu o captează – cu toate acestea, odată ce „iese” din înlocuirea comenzii, efectiv merge în continuare la descriptorul general al fișierului scriptului 1.

(exec 4>&1 are să fie o comandă separată, deoarece multe shell-uri obișnuite nu le place atunci când încercați să scrieți într-un descriptor de fișier într-o substituție de comandă, care este deschisă în comanda „externă” care folosește substituția. Deci acesta este cel mai simplu mod portabil să o faceți.)

Puteți să o priviți într-un mod mai puțin tehnic și mai jucăuș, ca și cum ieșirile comenzilor aruncau reciproc: comanda1 țeavă la comanda2, apoi ieșirea imprimantei sare peste comanda 2, astfel încât comanda2 să nu o prindă, iar apoi ieșirea comenzii 2 sări peste și din afară Înlocuirea comenzii la fel cum printf aterizează exact la timp pentru a fi capturată de substituție, astfel încât aceasta să ajungă în variabilă, iar ieșirea comandă 2 „merge în felul său vesel, fiind scrisă la ieșirea standard, la fel ca într-o conductă normală.

De asemenea, după cum înțeleg, $? va conține în continuare codul de returnare a celei de-a doua comenzi din conductă, deoarece atribuțiile variabile, substituțiile de comenzi și comenzile compuse sunt toate transparente în mod eficient la codul de returnare al comenzii din interiorul lor, astfel încât starea de returnare a comenzii2 ar trebui să fie propagată – aceasta și nefiind necesară definirea unei funcții suplimentare, de aceea cred că aceasta ar putea fi o soluție oarecum mai bună decât cea propusă de lesmana.

Conform avertismentelor menționate de lesmana, este posibil ca comanda1 să ajungă la un moment dat folosind descriptorii de fișiere 3 sau 4, deci pentru a fi mai robust, ați face:

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

Rețineți că folosesc comenzi compuse în exemplul meu, dar subshells (folosind ( ) în loc de { } va funcționa, deși poate poate fi mai puțin eficient.)

Comenzile moștenesc descriptorii de fișiere din proces care le lansează, deci întreaga a doua linie va moșteni descriptorul de fișier patru, iar comanda compusă urmată de 3>&1 va moșteni descriptorul de fișiere trei. Deci, 4>&- se asigură că comanda interioară compusă nu va moșteni descriptorul de fișier patru, iar 3>&- nu va moșteni descriptorul de fișiere trei, deci command1 primește un mediu „mai curat”, mai standard. De asemenea, ați putea muta 4>&- interior lângă 3>&-, dar îmi dau seama de ce să nu-i limităm domeniul de aplicare cât mai mult posibil.

Nu sunt sigur cât de des lucrurile folosesc direct descriptorul de fișiere trei și patru – cred că de cele mai multe ori programele folosesc syscalls care returnează descriptori de fișiere neutilizați la momentul respectiv, dar uneori codul scrie în fișier descriptorul 3 direct, cred (mi-aș putea imagina un program verificând un descriptor de fișier pentru a vedea dacă este deschis și îl folosește dacă este, sau se comportă diferit în consecință dacă nu este). Deci, acesta din urmă este probabil cel mai bine de păstrat în minte și de utilizat pentru cazuri cu scop general.

Comentarii

  • Pare interesant, dar pot ‘ Nu-mi dau seama ce așteptați să facă această comandă, iar computerul meu poate ‘ t, fie; primesc -bash: 3: Bad file descriptor.
  • @ G-Man Corect, uit totuși că bash nu are idee ce face ‘ mes to descriptori de fișiere, spre deosebire de shell-urile pe care le folosesc de obicei (cenușa care vine cu busybox). ‘ vă voi anunța când mă gândesc la o soluție care îi face pe bash fericit. Între timp, dacă ‘ ai o cutie debian la îndemână, o poți încerca în liniuță sau dacă ‘ ți-a venit busybox la îndemână îl puteți încerca cu busybox ash / sh.
  • @ G-Man În ceea ce mă aștept să facă comanda și ce face în alte shell-uri, este redirecționarea stdout de la comanda1, așa că nu ‘ nu rămâne prins de substituirea comenzii, dar odată ce a ieșit din substituția comenzii, acesta cade fd3 înapoi la stdout, așa că ‘ este canalizat conform așteptărilor a comanda2.Când comanda1 iese, printf declanșează și imprimă starea de ieșire, care este capturată în variabilă prin substituirea comenzii. Defalcare foarte detaliată aici: stackoverflow.com/questions/985876/tee-and-exit-status/… De asemenea , comentariul tău a citit de parcă ar fi fost oarecum jignitor?
  • De unde să încep? (1) Îmi pare rău dacă v-ați simțit insultat. „Pare interesant” se înțelegea cu seriozitate; ar fi minunat dacă ceva la fel de compact ca acela să funcționeze la fel de bine cum te-ai așteptat. Dincolo de asta, spuneam, pur și simplu, că nu înțelegeam ce trebuia să facă soluția ta. Lucrez / mă joc cu Unix de mult timp (de când a existat Linux) și, dacă nu înțeleg ceva, acesta este un steag roșu care, poate, alte persoane nu o vor înțelege și că au nevoie de mai multe explicații (IMNSHO). … (Continuare)
  • (Continuare)… Deoarece tu “… îți place să crezi… că [tu] înțelegi doar despre totul mai mult decât persoana obișnuită ”, poate ar trebui să ne amintim că obiectivul Stack Exchange nu este să fie un serviciu de scriere a comenzilor, care să producă mii de soluții unice la întrebări banale distincte; ci mai degrabă să-i învețe pe oameni cum să-și rezolve propriile probleme. Și, în acest scop, poate că trebuie să explicați lucrurile suficient de bine încât o „persoană obișnuită” să o înțeleagă. Uită-te la răspunsul lesmana pentru un exemplu de explicație excelentă. … (Continuare)

Răspuns

lesmana „soluția de mai sus se poate face și fără cheltuielile generale ale pornirea subproceselor imbricate folosind în schimb { .. } (amintind că această formă de comenzi grupate trebuie să se încheie întotdeauna cu punct și virgulă). Ceva de genul acesta:

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

Am „verificat această construcție cu versiunea de liniuță 0.5.5 și versiunile bash 3.2.25 și 4.2.42, deci chiar dacă unele shell-uri nu acceptă { .. } grupare, este încă compatibil POSIX.

Comentarii

  • Acest lucru funcționează foarte bine cu majoritatea shell-urilor I ‘ l-am încercat cu, inclusiv NetBSD sh, pdksh, mksh, dash, bash. Cu toate acestea, nu pot ‘ să-l fac să funcționeze cu AT & T Ksh (93s +, 93u +) sau zsh (4.3.9, 5.2), chiar și cu set -o pipefail în ksh sau orice număr de wait comandă în oricare dintre ele. Cred că poate, parțial cel puțin, să fie o problemă de analiză pentru ksh, ca și cum aș rămâne la utilizarea subshells atunci funcționează bine, dar chiar și cu un if pentru a alege varianta subshell pentru ksh, dar lăsați comenzile compuse pentru alții, nu reușește.

Răspuns

Următorul este menit ca un supliment la răspunsul lui @Patrik, în cazul în care nu puteți utiliza una dintre soluțiile obișnuite.

Acest răspuns presupune următoarele:

  • Aveți un shell care nu știe de $PIPESTATUS și nici set -o pipefail
  • Doriți să utilizați o țeavă pentru executarea paralelă, deci nu există fișiere temporare.
  • nu doriți să aveți o aglomerație suplimentară dacă întrerupeți scriptul, posibil printr-o întrerupere bruscă de curent.
  • Această soluție ar trebui să fie relativ ușor de urmărit și curată de citit.
  • nu doriți să introduceți sub-shell-uri suplimentare.
  • Nu puteți juca cu descriptorii de fișiere existenți, așa că stdin / out / err nu trebuie atins (cum puteți introduce vreodată unul nou temporar)

Ipoteze suplimentare. Puteți scăpa de toate, dar acest lucru împiedică prea mult rețeta, deci nu este acoperită aici:

  • Tot ce doriți să știți este că toate comenzile din PIPE au codul de ieșire 0.
  • Nu aveți nevoie de informații suplimentare despre banda laterală.
  • Shell-ul dvs. așteaptă să revină toate comenzile de canalizare.

Înainte: foo | bar | baz, totuși acesta returnează doar codul de ieșire al ultimei comenzi (baz)

Wanted: $? nu trebuie să fie 0 (adevărat), dacă oricare dintre comenzile din țeavă a eșuat

După:

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 

Explicat:

  • Se creează un fișier temporar cu mktemp. De obicei, acest lucru creează imediat un fișier în /tmp
  • Acest fișier temporal este apoi redirecționat către FD 9 pentru scriere și FD 8 pentru citire
  • Apoi tempfile este imediat șters. Totuși, rămâne deschis până când ambele FD vor dispărea.
  • Acum, conducta este pornită. Fiecare pas se adaugă numai la FD 9, dacă a apărut o eroare.
  • wait este necesar pentru ksh, deoarece ksh altcineva nu așteaptă finalizarea tuturor comenzilor de conducte. Cu toate acestea, vă rugăm să rețineți că există efecte secundare nedorite dacă sunt prezente unele sarcini de fundal, așa că am comentat-o în mod implicit.Dacă așteptarea nu dăunează, o puteți comenta.
  • După aceea, se citește conținutul fișierului. Dacă este gol (pentru că totul a funcționat) read returnează false, deci true indică o eroare

Acesta poate fi folosit ca înlocuitor de plugin pentru o singură comandă și are nevoie doar de următoarele:

  • FD-urile 9 și 8 neutilizate
  • O singură variabilă de mediu care să dețină numele fișierului temporar
  • Și aceasta rețeta poate fi adaptată la orice shell care permite redirecționarea IO
  • De asemenea, este destul de agnostic de platformă și nu are nevoie de lucruri precum /proc/fd/N

BUG-uri:

Acest script are o eroare în cazul în care /tmp rămâne fără spațiu. Dacă aveți nevoie și de protecție împotriva acestui caz artificial, o puteți face după cum urmează, cu toate acestea acest lucru are dezavantajul, că numărul de 0 din 000 depinde de numărul de comenzi dinpipe, deci este puțin mai complicat:

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" 

Note de portabilitate:

  • ksh și cochilii similare care așteaptă doar ultima comandă de pipă au nevoie de wait necomentatate

  • Ultimul exemplu folosește printf "%1s" "$?" în loc de echo -n "$?" deoarece acesta este mai portabil. Nu fiecare platformă interpretează corect -n corect.

  • printf "$?" ar face-o și la fel, cu toate acestea, printf "%1s" surprinde câteva cazuri de colț în cazul în care rulați scriptul pe o platformă cu adevărat spartă. (Citiți: dacă se întâmplă să programați în paranoia_mode=extreme.)

  • FD 8 și FD 9 pot fi mai mari pe platformele care acceptă mai multe cifre. AFAIR un shell conform POSIX trebuie doar să accepte cifre simple.

  • A fost testat cu Debian 8.2 sh, bash, ksh, ash, sash și chiar csh

Răspuns

Acesta este portabil, adică funcționează cu orice shell compatibil POSIX, nu necesită ca directorul curent să poată fi scris și permite ca mai multe scripturi care utilizează același truc să ruleze simultan.

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

Editați: iată o versiune mai puternică după comentariile lui Gilles:

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

Edit2: și iată o variantă ușor mai ușoară după comentariul dubiousjim:

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

Comentarii

  • Acest lucru nu funcționează din mai multe motive. ‘ 1. Fișierul temporar poate fi citit înainte de a fi scris ‘. 2. Crearea unui fișier temporar într-un director partajat cu un nume previzibil este nesigură (DoS trivial, cursă simbolică). 3. Dacă același script folosește acest truc de mai multe ori, ‘ va folosi întotdeauna același nume de fișier. Pentru a rezolva 1, citiți fișierul după finalizarea conductei. Pentru a rezolva 2 și 3, utilizați un fișier temporar cu un nume generat aleatoriu sau într-un director privat.
  • +1 Ei bine, $ {PIPESTATUS [0]} este mai ușor, dar ideea de bază aici funcționează dacă se știe despre problemele menționate de Gilles.
  • Puteți salva câteva sub-cochilii: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Sunt de acord că ‘ este mai ușor cu Bash, dar în anumite contexte, știu cum să eviți Bash merită.

Răspuns

Cu un pic de precauție, acest lucru ar trebui să funcționeze:

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

Comentarii

  • Ce zici de curățare ca jlliagre? Nu ‘ nu lăsați în urmă un fișier numit foo-status?
  • @Johan: Dacă preferați sugestia mea, nu ‘ nu ezitați să îl votați;) Pe lângă faptul că nu părăsiți un fișier, acesta are avantajul de a permite mai multe procese să ruleze simultan și directorul curent nu trebuie să fie înscris.

Răspuns

Următorul bloc „if” va rula numai dacă „command” a reușit:

if command; then # ... fi 

Mai exact, puteți rula așa ceva:

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

Care va rula haconf -makerw și stocați stdout și stderr la „$ haconf_out”. Dacă valoarea returnată din haconf este adevărată, atunci blocul „if” va fi executat și grep va citi „$ haconf_out”, încercând pentru a-l potrivi cu „Cluster deja scriibil”.

Observați că conductele se curăță automat; cu redirecționarea, va trebui să fiți atent pentru a elimina „$ haconf_out” când ați terminat.

Nu la fel de elegant ca pipefail, dar o alternativă legitimă dacă această funcționalitate nu este la îndemână.

Răspuns

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 $ 

Răspuns

(Cu bash cel puțin) combinat cu set -e se poate utiliza subshell pentru a emula în mod explicit pipefail și a ieși din eroarea pipe

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

Deci, dacă foo eșuează dintr-un anumit motiv – restul programului nu va fi executat și scriptul iese cu codul de eroare corespunzător. (Aceasta presupune că foo își imprimă propria eroare, care este suficientă pentru a înțelege motivul eșecului)

Răspuns

EDIT : Acest răspuns este greșit, dar interesant, așa că îl voi lăsa pentru viitor referință.


Adăugarea unui ! la comandă inversează codul de returnare.

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

Comentarii

  • Cred că acest lucru nu are legătură. În exemplul dvs., vreau să știu codul de ieșire al ls, nu să inversați codul de ieșire al bogus_command
  • Vă sugerez să retrageți răspunsul respectiv.
  • Ei bine, se pare că eu ‘ sunt un idiot. I ‘ De fapt, am folosit acest lucru într-un script înainte să cred că a făcut ceea ce dorea OP. Bine că nu l-am folosit ‘ nu l-am folosit pentru nimic important

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *