Ottieni lo stato di uscita del processo che ' è reindirizzato a un altro

Ho due processi foo e bar, collegati con una pipe:

$ foo | bar 

bar esce sempre da 0; Sono interessato al codice di uscita di foo. Cè un modo per ottenerlo?

Commenti

Risposta

bash e zsh hanno una variabile array che contiene lo stato di uscita di ogni elemento (comando) dellultima pipeline eseguita dalla shell.

Se stai usando bash, larray si chiama PIPESTATUS (il caso è importante!) e gli indici dellarray iniziano da zero:

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

Se utilizzi zsh, larray si chiama pipestatus (il caso è importante!) e gli indici dellarray iniziano da uno:

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

Per combinarli allinterno di una funzione in un modo che non perda i valori:

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

Esegui quanto sopra in bash o zsh e otterrai gli stessi risultati; verrà impostato solo uno tra retval_bash e retval_zsh. Laltro sarà vuoto. Ciò consentirebbe a una funzione di terminare con return $retval_bash $retval_zsh (nota la mancanza di virgolette!).

Commenti

  • e pipestatus in zsh. Purtroppo altre shell non ‘ hanno questa funzione.
  • Nota: gli array in zsh iniziano in modo controintuitivo allindice 1, quindi ‘ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Puoi controllare lintera pipeline in questo modo: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @JanHudec: Forse dovresti leggere le prime cinque parole della mia risposta. Indica anche gentilmente dove la domanda richiedeva una risposta solo POSIX.
  • @JanHudec: Né era etichettata POSIX. Perché pensi che la risposta debba essere POSIX? Non è stato specificato quindi ho fornito una risposta qualificata. Non cè niente di sbagliato nella mia risposta, inoltre ci sono più risposte per affrontare altri casi.

Risposta

Lì ci sono 3 modi comuni per farlo:

Pipefail

Il primo modo è impostare lopzione pipefail (ksh, zsh o bash). Questo è il più semplice e ciò che fa è fondamentalmente impostare lo stato di uscita $? sul codice di uscita dellultimo programma per uscire diverso da zero (o zero se tutti sono usciti correttamente).

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

$ PIPESTATUS

Bash ha anche una variabile array chiamata $PIPESTATUS ($pipestatus in zsh) che contiene lo stato di uscita di tutti i programmi nellultima 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 

Puoi utilizzare il terzo esempio di comando per ottenere il valore specifico nella pipeline di cui hai bisogno.

Esecuzioni separate

Questa è la soluzione più ingombrante. Esegui ogni comando separatamente e acquisisci lo stato

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

Commenti

  • Dannazione! Volevo solo postare su PIPESTATUS.
  • Per riferimento ci sono molte altre tecniche discusse in questa domanda SO: stackoverflow.com/questions/1221833/…
  • @Patrick la soluzione pipestatus funziona su bash, solo più quastion nel caso in cui utilizzo lo script ksh pensi che possiamo trovare qualcosa di simile a pipestatus? , (mentre vedo il pipestatus non supportato da ksh)
  • @yael Non ‘ t utilizzare ksh, ma da una rapida occhiata alla ‘ manpage, non ‘ t supporta $PIPESTATUS o qualcosa di simile. Tuttavia, supporta lopzione pipefail.
  • Ho deciso di utilizzare pipefail in quanto mi consente di ottenere lo stato del comando non riuscito qui: LOG=$(failed_command | successful_command)

Risposta

Questa soluzione funziona senza utilizzare funzionalità specifiche di bash o file temporanei . Bonus: alla fine lo stato di uscita è in realtà uno stato di uscita e non una stringa in un file.

Situazione:

someprog | filter 

tu voglio lo stato di uscita da someprog e loutput da filter.

Ecco la mia soluzione:

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

il risultato di questo costrutto è stdout da filter come stdout del costrutto e stato di uscita da someprog come stato di uscita del costrutto.


questo costrutto funziona anche con un semplice raggruppamento di comandi {...} invece di subshell (...). le subshell hanno alcune implicazioni, tra cui un costo in termini di prestazioni, di cui non abbiamo bisogno qui. leggi il fine manuale di bash per maggiori dettagli: 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 

Sfortunatamente la grammatica bash richiede spazi e punti e virgola per le parentesi graffe in modo che il costrutto diventi molto più spazioso.

Per il resto di questo testo userò la variante subshell.


Esempio someprog e 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 $? 

Esempio di output:

filtered line1 filtered line2 filtered line3 42 

Nota: il processo figlio eredita i descrittori di file aperti dal genitore. Ciò significa che someprog erediterà il descrittore di file aperto 3 e 4. Se someprog scrive nel descrittore di file 3, quello diventerà lo stato di uscita. Il vero stato di uscita verrà ignorato perché read legge solo una volta.

Se temi che il tuo someprog possa scrivere al descrittore di file 3 o 4, è meglio chiudere i descrittori di file prima di chiamare someprog.

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

Il exec 3>&- 4>&- prima che someprog chiuda il descrittore di file prima di eseguire someprog so per someprog questi descrittori di file semplicemente non esistono.

Può anche essere scritto in questo modo: someprog 3>&- 4>&-


Spiegazione passo passo del costrutto:

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

Dal basso verso lalto:

  1. Viene creata una subshell con descrittore di file 4 reindirizzato a stdout. Ciò significa che qualsiasi cosa venga stampata nel descrittore di file 4 nella subshell finirà come lo stdout dellintero costrutto.
  2. Viene creata una pipe e i comandi a sinistra (#part3) e destra (#part2) vengono eseguiti. exit $xs è anche lultimo comando della pipe e ciò significa che la stringa da stdin sarà lo stato di uscita dellintero costrutto.
  3. Viene creata una subshell con file descrittore 3 reindirizzato a stdout. Ciò significa che qualsiasi cosa venga stampata nel descrittore di file 3 in questa subshell finirà in #part2 e, a sua volta, sarà lo stato di uscita dellintero costrutto.
  4. A pipe viene creato e i comandi a sinistra (#part5 e #part6) e a destra (filter >&4) vengono eseguiti. Loutput di filter viene reindirizzato al descrittore di file 4. In #part1 il descrittore di file 4 è stato reindirizzato a stdout. Ciò significa che loutput di filter è lo stdout dellintero costrutto.
  5. Viene stampato lo stato di uscita da #part6 al descrittore di file 3. In #part3 il descrittore di file 3 è stato reindirizzato a #part2. Ciò significa che lo stato di uscita da #part6 sarà lo stato di uscita finale per lintero costrutto.
  6. someprog è eseguito. Lo stato di uscita è preso in #part5. Lo stdout viene preso dalla pipe in #part4 e inoltrato a filter. Loutput di filter raggiungerà a sua volta lo stdout come spiegato in #part4

Commenti

  • Bello. Per la funzione potrei semplicemente fare (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) semplifica in someprog 3>&- 4>&-.
  • Questo metodo funziona anche senza subshell: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Risposta

Sebbene non sia esattamente quello che hai chiesto, potresti usare

#!/bin/bash -o pipefail 

in modo che le tue pipe restituiscano lultimo ritorno diverso da zero.

potrebbe essere un po meno codificato

Modifica: esempio

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

Commenti

  • set -o pipefail allinterno dello script dovrebbe essere più robusto, ad es. nel caso qualcuno esegua lo script tramite bash foo.sh.
  • Come funziona? hai un esempio?
  • Nota che -o pipefail non è in POSIX.
  • Questo non funziona nel mio BASH 3.2.25 ( 1) -release. Nella parte superiore di / tmp / ff ho #!/bin/bash -o pipefail.Lerrore è: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: alcuni ambienti (incluso linux) don ‘ t analizzano gli spazi su #! righe oltre la prima, quindi questo diventa /bin/bash -o pipefail /tmp/ff, invece del necessario /bin/bash -o pipefail /tmp/ffgetopt (o simile) analisi utilizzando optarg, che è lelemento successivo in ARGV, come argomento di -o, quindi non riesce. Se dovessi creare un wrapper (ad esempio bash-pf che ha appena exec /bin/bash -o pipefail "$@" e inserirlo nel #!, che funzionerebbe. Vedi anche: en.wikipedia.org/wiki/Shebang_%28Unix%29

Risposta

Quello che faccio quando possibile è inserire il codice di uscita da foo a bar. Ad esempio, se so che foo non produce mai una riga con solo cifre, posso semplicemente aggiungere il codice di uscita:

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

O se so che loutput di foo non contiene mai una riga con solo .:

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

Questo può essere sempre fatto se cè un modo per ottenere bar per lavorare su tutte tranne lultima riga e passare lultima riga come codice di uscita.

Se bar è una pipeline complessa il cui output non è necessario, puoi ignorarne una parte stampando il codice di uscita su un descrittore di file diverso.

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

Dopo questo $exit_codes di solito è foo:X bar:Y, ma potrebbe essere bar:Y foo:X se bar esce prima di leggere tutto il suo input o se sei sfortunato. Penso che le scritture su pipe fino a 512 byte siano atomiche su tutti gli Unix, quindi le parti foo:$? e bar:$? non verranno mischiate come fintanto che le stringhe dei tag sono inferiori a 507 byte.

Se devi acquisire loutput da bar, diventa difficile. Puoi combinare le tecniche precedenti disponendo affinché loutput di bar non contenga mai una riga che assomiglia a unindicazione del codice di uscita, ma diventa complicata.

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

E, naturalmente, cè la semplice opzione di utilizzare un file temporaneo per memorizzare lo stato. Semplice, ma non così semplice nella produzione:

  • Se ci sono più script in esecuzione contemporaneamente, o se lo stesso script utilizza questo metodo in più punti, è necessario creare assicurati che utilizzino nomi di file temporanei diversi.
  • Creare un file temporaneo in modo sicuro in una directory condivisa è difficile. Spesso /tmp è lunico posto in cui uno script è sicuramente in grado di scrivere file. Utilizza mktemp , che non è POSIX ma disponibile su tutti gli Unix importanti al giorno doggi.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Commenti

  • Quando si utilizza lapproccio ai file temporanei, preferisco aggiungere una trap per EXIT che rimuove tutti i file temporanei in modo che non rimanga spazzatura anche se lo script muore

Answer

A partire dalla pipeline:

foo | bar | baz 

Ecco una soluzione generale che utilizza solo la shell POSIX e nessun file temporaneo:

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

$error_statuses contiene i codici di stato di eventuali processi non riusciti, in ordine casuale, con indici per indicare quale comando ha emesso ogni stato.

# 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 

Nota le virgolette intorno a $error_statuses nei miei test; senza di essi grep non può “distinguere perché i caratteri di ritorno a capo vengono forzati negli spazi.

Risposta

Se hai installato il pacchetto moreutils puoi utilizzare lutilità mispipe che fa esattamente quello che hai chiesto.

Risposta

Quindi volevo contribuire con una risposta come lesmana “s, ma penso che la mia sia forse un po più semplice e leggermente più vantaggiosa pure- Soluzione 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. 

Penso che sia meglio spiegato dallinterno – command1 eseguirà e stamperà il suo normale output su stdout (descrittore di file 1) , poi una volta fatto, printf eseguirà e stamperà il codice di uscita del comando1 sul suo stdout, ma quello stdout viene reindirizzato al descrittore di file 3.

Mentre command1 è in esecuzione, il suo stdout viene reindirizzato a comando2 (loutput di printf non arriva mai a comando2 perché lo inviamo al descrittore di file 3 invece di 1, che io s quello che legge la pipa).Quindi reindirizziamo loutput di command2 al descrittore di file 4, in modo che rimanga fuori dal descrittore di file 1 – perché vogliamo che il descrittore di file 1 sia libero per un po più tardi, perché riporteremo loutput di printf sul descrittore di file 3 descrittore di file 1 – perché questo è ciò che verrà catturato dalla sostituzione del comando (i backtick) e questo è ciò che verrà inserito nella variabile.

Lultima punta di magia è che il primo exec 4>&1 abbiamo eseguito come comando separato: apre il descrittore di file 4 come una copia dello stdout della shell esterna. La sostituzione del comando catturerà tutto ciò che è scritto sullo standard dalla prospettiva dei comandi al suo interno – ma, poiché loutput di command2 sta andando al descrittore di file 4 per quanto riguarda la sostituzione del comando, la sostituzione del comando non lo cattura – tuttavia, una volta che è “uscito” dalla sostituzione del comando, in effetti va ancora al descrittore di file generale dello script 1.

(Il exec 4>&1 ha essere un comando separato perché a molte shell comuni non piace quando si tenta di scrivere in un descrittore di file allinterno di una sostituzione di comando, che viene aperta nel comando “esterno” che sta usando la sostituzione. Quindi questo è il modo più semplice portatile per farlo.)

Puoi guardarlo in un modo meno tecnico e più giocoso, come se gli output dei comandi si spostassero a vicenda: command1 si indirizza a command2, quindi loutput di printf salta sul comando 2 in modo che comando2 non lo rilevi, e quindi loutput del comando 2 salta La sostituzione del comando proprio come printf arriva appena in tempo per essere catturato dalla sostituzione in modo che finisca nella variabile, e loutput di command2 continua a essere scritto sullo standard output, proprio come in una normale pipe.

Inoltre, a quanto ho capito, $? conterrà ancora il codice di ritorno del secondo comando nella pipe, perché assegnazioni di variabili, sostituzioni di comandi e comandi composti sono tutto efficacemente trasparente al codice di ritorno del comando al loro interno, quindi lo stato di ritorno di comando2 dovrebbe essere propagato – questo, e non dover definire una funzione aggiuntiva, è il motivo per cui penso che questa potrebbe essere una soluzione un po migliore di quella proposta da lesmana.

Per gli avvertimenti menzionati da lesmana, è possibile che command1 ad un certo punto finirà per usare i descrittori di file 3 o 4, quindi per essere più robusto, dovresti:

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

Nota che utilizzo comandi composti nel mio esempio, ma subshell (utilizzando ( ) invece di { }, anche se potrebbe essere meno efficiente.)

I comandi ereditano i descrittori di file dal processo che li avvia, quindi lintera seconda riga erediterà il descrittore di file quattro, e il comando composto seguito da 3>&1 erediterà il descrittore di file tre. Quindi 4>&- si assicura che il comando composto interno non erediti il descrittore di file quattro e 3>&- non erediterà il descrittore di file tre, quindi command1 ottiene un ambiente “più pulito” e più standard. Puoi anche spostare il 4>&- interno accanto a 3>&-, ma immagino perché non limitarne lambito il più possibile.

Non sono sicuro di quanto spesso le cose usino direttamente i descrittori di file tre e quattro – penso che la maggior parte delle volte i programmi usino syscall che restituiscono descrittori di file non utilizzati al momento, ma a volte il codice scrive su file descrittore 3 direttamente, immagino (potrei immaginare un programma che controlla un descrittore di file per vedere se è aperto e lo usa se lo è, o si comporta diversamente di conseguenza se non lo è) .Quindi è probabilmente meglio mantenere questultimo in mente e da usare per casi generici.

Commenti

  • Sembra interessante, ma posso ‘ Non capisco bene cosa ti aspetti da questo comando e il mio computer può ‘ t; ottengo -bash: 3: Bad file descriptor.
  • @ G-Man Right, continuo a dimenticarmi che bash non ha idea di cosa fa ‘ quando co mes ai descrittori di file, a differenza delle shell che uso di solito (lash fornito con busybox). Ti ‘ ti farò sapere quando penso a una soluzione alternativa che renda felice bash. Nel frattempo, se ‘ hai una scatola Debian a portata di mano puoi provarla in dash, o se ‘ hai busybox a portata di mano può provarlo con il busybox ash / sh.
  • @ G-Man Per quanto riguarda ciò che mi aspetto che il comando faccia e ciò che fa in altre shell, è reindirizzare stdout da command1 quindi non ‘ non viene intercettato dalla sostituzione del comando, ma una volta fuori dalla sostituzione del comando, restituisce fd3 allo stdout, quindi ‘ viene reindirizzato come previsto a command2.Quando command1 esce, printf si attiva e stampa il suo stato di uscita, che viene catturato nella variabile dalla sostituzione del comando. Analisi molto dettagliata qui: stackoverflow.com/questions/985876/tee-and-exit-status/… Inoltre , quel tuo commento letto come se volesse essere un po offensivo?
  • Da dove comincio? (1) Mi dispiace se ti sei sentito insultato. “Sembra interessante” era inteso seriamente; sarebbe fantastico se qualcosa di così compatto funzionasse come ti aspettavi. Oltre a ciò, stavo dicendo, semplicemente, che non capivo cosa avrebbe dovuto fare la tua soluzione. Ho lavorato / giocato con Unix per un lungo tempo (da prima che esistesse Linux) e, se non capisco qualcosa, è una bandiera rossa che, forse, Anche le altre persone non lo capiranno e che necessita di ulteriori spiegazioni (IMNSHO). … (Continua)
  • (Continua)… Dato che tu “… piace pensare … che [tu] capisci quasi tutto più della persona media ”, forse dovresti ricordare che lobiettivo di Stack Exchange non è quello di essere un servizio di scrittura di comandi, sfornando migliaia di soluzioni una tantum a domande banalmente distinte; ma piuttosto insegnare alle persone come risolvere i propri problemi. E, a tal fine, forse è necessario spiegare le cose abbastanza bene in modo che una “persona media” possa capirle. Guarda la risposta di lesmana per un esempio di unottima spiegazione. … (Continua)

Risposta

La soluzione di lesmana “sopra può essere eseguita anche senza il sovraccarico di avviare sottoprocessi nidificati utilizzando invece { .. } (ricordando che questa forma di comandi raggruppati deve sempre finire con punto e virgola). Qualcosa di simile:

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

Ho verificato questo costrutto con dash versione 0.5.5 e bash versioni 3.2.25 e 4.2.42, quindi anche se alcune shell non supportano { .. } raggruppamento, è ancora conforme a POSIX.

Commenti

  • Funziona molto bene con la maggior parte delle shell I ‘ lho provato con, incluso NetBSD sh, pdksh, mksh, dash, bash. Tuttavia non posso ‘ farlo funzionare con AT & T Ksh (93s +, 93u +) o zsh (4.3.9, 5.2), anche con set -o pipefail in ksh o qualsiasi numero di wait comandi in entrambi. Penso che possa, in parte almeno, è un problema di analisi per ksh, come se continuassi a usare le subshell allora funziona bene, ma anche con un if per scegliere la variante della subshell per ksh ma lasciare i comandi composti per altri, non riesce.

Risposta

Quanto segue è inteso come un addon alla risposta di @Patrik, nel caso in cui non sei in grado di utilizzare una delle soluzioni comuni.

Questa risposta presuppone quanto segue:

  • Hai una shell che non conosce $PIPESTATUSset -o pipefail
  • Vuoi utilizzare una pipe per lesecuzione parallela, quindi niente file temporanei.
  • Tu non vuoi avere ulteriore confusione in giro se interrompi lo script, probabilmente a causa di uninterruzione di corrente improvvisa.
  • Questa soluzione dovrebbe essere relativamente facile da seguire e pulita da leggere.
  • non voglio introdurre subshell aggiuntive.
  • Non puoi giocherellare con i descrittori di file esistenti, quindi stdin / out / err non deve essere toccato (come mai se ne può introdurre uno nuovo temporaneamente)

Presupposti aggiuntivi. Puoi sbarazzarti di tutto, ma questo ostacola troppo la ricetta, quindi non è trattato qui:

  • Tutto quello che vuoi sapere è che tutti i comandi in PIPE hanno il codice di uscita 0.
  • Non sono necessarie informazioni aggiuntive sulla banda laterale.
  • La shell attende la restituzione di tutti i comandi pipe.

Prima: foo | bar | baz, tuttavia restituisce solo il codice di uscita dellultimo comando (baz)

Ricercato: $? non deve essere 0 (true), se uno dei comandi nella pipe non è riuscito

Dopo:

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 

spiegato:

  • Viene creato un file temporaneo con mktemp. Questo di solito crea immediatamente un file in /tmp
  • Questo file temporaneo viene quindi reindirizzato a FD 9 per la scrittura e FD 8 per la lettura
  • Quindi il tempfile viene immediatamente eliminato. Rimane aperto, tuttavia, fino a quando entrambi gli FD non scompaiono.
  • Ora il pipe viene avviato. Ogni passaggio si aggiunge a FD 9 solo se si è verificato un errore.
  • wait è necessario per ksh, perché ksh else non attende il completamento di tutti i comandi pipe. Tuttavia, tieni presente che ci sono effetti collaterali indesiderati se sono presenti alcune attività in background, quindi lho commentato per impostazione predefinita.Se lattesa non fa male, puoi commentarla.
  • Successivamente viene letto il contenuto del file. Se è vuoto (perché tutto ha funzionato) read restituisce false, quindi true indica un errore

Questo può essere utilizzato come sostituto del plug-in per un singolo comando e necessita solo di quanto segue:

  • FD non utilizzati 9 e 8
  • Una singola variabile dambiente per contenere il nome del file temp
  • E questo la ricetta può essere adattata praticamente a qualsiasi shell là fuori che consente il reindirizzamento IO
  • Inoltre è abbastanza indipendente dalla piattaforma e non ha bisogno di cose come /proc/fd/N

BUG:

Questo script ha un bug nel caso in cui /tmp si esaurisca. Se hai bisogno di protezione anche contro questo caso artificiale, puoi farlo come segue, tuttavia questo ha lo svantaggio, che il numero di 0 in 000 dipende dal numero di comandi nelpipe, quindi è leggermente più complicato:

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 sulla portabilità:

  • ksh e shell simili che aspettano solo lultimo comando pipe richiedono il wait non commentato

  • Lultimo esempio usa printf "%1s" "$?" invece di echo -n "$?" perché è più portabile. Non tutte le piattaforme interpretano -n correttamente.

  • printf "$?" lo farebbe anche, tuttavia printf "%1s" rileva alcuni casi dangolo nel caso in cui esegui lo script su una piattaforma davvero rotta. (Leggi: se ti capita di programmare in paranoia_mode=extreme.)

  • FD 8 e FD 9 possono essere più alti su piattaforme che supportano più cifre. AFAIR una shell conforme a POSIX deve supportare solo cifre singole.

  • È stata testata con Debian 8.2 sh, bash, ksh, ash, sash e anche csh

Answer

Questo è portatile, cioè funziona con qualsiasi shell conforme a POSIX, non richiede che la directory corrente sia scrivibile e consente lesecuzione simultanea di più script che utilizzano lo stesso trucco.

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

Modifica: ecco una versione più forte dopo i commenti di Gilles:

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

Edit2: ed ecco una variante leggermente più leggera dopo il commento di dubiousjim:

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

Commenti

  • Questo ‘ non funziona per diversi motivi. 1. Il file temporaneo può essere letto prima che venga ‘ scritto. 2. La creazione di un file temporaneo in una directory condivisa con un nome prevedibile non è sicura (DoS banale, corsa di collegamento simbolico). 3. Se lo stesso script utilizza questo trucco più volte, ‘ utilizzerà sempre lo stesso nome di file. Per risolvere 1, leggi il file dopo che la pipeline è stata completata. Per risolvere 2 e 3, utilizza un file temporaneo con un nome generato casualmente o in una directory privata.
  • +1 Bene, $ {PIPESTATUS [0]} è più semplice, ma lidea di base qui funziona se si conoscono i problemi menzionati da Gilles.
  • È possibile salvare alcune subshell: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Sono daccordo che ‘ è più facile con Bash ma in alcuni contesti vale la pena sapere come evitare Bash.

Risposta

Con un po di precauzione, dovrebbe funzionare:

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

Commenti

  • Che ne dici di pulire come jlliagre? Non ‘ lasciare un file chiamato foo-status?
  • @Johan: Se preferisci il mio suggerimento, don ‘ esita a votare;) Oltre a non lasciare un file, ha il vantaggio di consentire a più processi di eseguirlo contemporaneamente e la directory corrente non deve essere scrivibile.

Risposta

Il seguente blocco “if” verrà eseguito solo se il “comando” è riuscito:

if command; then # ... fi 

In particolare, puoi eseguire qualcosa di simile:

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

Che verrà eseguito haconf -makerw e memorizza i suoi stdout e stderr in “$ haconf_out”. Se il valore restituito da haconf è vero, il blocco “if” verrà eseguito e grep leggerà “$ haconf_out”, provando per confrontarlo con “Cluster già scrivibile”.

Notare che i pipe si puliscono automaticamente; con il reindirizzamento dovrai stare attento a rimuovere “$ haconf_out” quando hai finito.

Non elegante come pipefail, ma unalternativa legittima se questa funzionalità non è a portata di mano.

Risposta

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 $ 

Risposta

(Almeno con bash) combinato con set -e è possibile utilizzare subshell per emulare esplicitamente il pipefail e uscire in caso di errore del pipe

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

Quindi, se foo fallisce per qualche motivo, il resto del programma non verrà eseguito e lo script verrà chiuso con il codice di errore corrispondente. (Ciò presuppone che foo stampi il proprio errore, che è sufficiente per comprendere il motivo del fallimento)

Risposta

EDIT : questa risposta è sbagliata, ma interessante, quindi la lascio per il futuro riferimento.


Aggiungendo un ! al comando si inverte il codice di ritorno.

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

Commenti

  • Penso che questo non sia correlato. Nel tuo esempio, voglio conoscere il codice di uscita di ls, non invertire il codice di uscita di bogus_command
  • Suggerisco di richiamare quella risposta.
  • Bene, sembra che ‘ sono un idiota. ‘ lho effettivamente usato in uno script prima di pensare che facesse quello che voleva lOP. Meno male che ‘ non lho usato per qualcosa di importante

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *