Obtenir létat de sortie du processus que ' est redirigé vers un autre

Jai deux processus foo et bar, connectés par un tuyau:

$ foo | bar 

bar sort toujours de 0; Je « suis intéressé par le code de sortie de foo. Y a-t-il un moyen dy accéder?

Commentaires

Answer

bash et zsh ont une variable de tableau qui contient létat de sortie de chaque élément (commande) du dernier pipeline exécuté par le shell.

Si vous utilisez bash, le tableau sappelle PIPESTATUS (la casse compte!) et les indications du tableau commencent à zéro:

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

Si vous utilisez zsh, le tableau sappelle pipestatus (la casse compte!) et les indices du tableau commencent à un:

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

Pour les combiner au sein dune fonction de manière à ne pas perdre les valeurs:

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

Exécutez ce qui précède dans bash ou zsh et vous « obtiendrez les mêmes résultats; un seul des retval_bash et retval_zsh sera défini. Lautre sera vide. Cela permettrait à une fonction de se terminer par return $retval_bash $retval_zsh (notez le manque de guillemets!).

Commentaires

  • Et pipestatus dans zsh. Malheureusement, d’autres shells n’ont pas ‘ cette fonctionnalité.
  • Remarque: les tableaux dans zsh commencent de manière contre-intuitive à l’index 1, donc ‘ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Vous pouvez vérifier lensemble du pipeline comme ceci: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @JanHudec: Vous devriez peut-être lire les cinq premiers mots de ma réponse. Veuillez également indiquer où la question demandait une réponse POSIX uniquement.
  • @JanHudec: Elle na pas non plus été balisée POSIX. Pourquoi pensez-vous que la réponse doit être POSIX? Ce nétait pas précisé, jai donc fourni une réponse nuancée. Il ny a rien dincorrect dans ma réponse, et il y a plusieurs réponses pour traiter dautres cas.

Réponse

Là sont 3 façons courantes de faire ceci:

Pipefail

La première façon est de définir loption pipefail (ksh, zsh ou bash). Cest le plus simple et ce quil fait est de définir le statut de sortie $? sur le code de sortie du dernier programme à quitter différent de zéro (ou zéro si tout est terminé avec succès).

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

$ PIPESTATUS

Bash a également une variable de tableau appelée $PIPESTATUS ($pipestatus in zsh) qui contient létat de sortie de tous les programmes du dernier 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 

Vous pouvez utiliser le troisième exemple de commande pour obtenir la valeur spécifique du pipeline dont vous avez besoin.

Exécutions séparées

Cest la solution la plus compliquée. Exécutez chaque commande séparément et saisissez létat

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

Commentaires

  • Darn! Jallais juste poster sur PIPESTATUS.
  • Pour référence, il y a plusieurs autres techniques discutées dans cette question SO: stackoverflow.com/questions/1221833/…
  • @Patrick la solution pipestatus fonctionne sur bash, juste plus de quastion au cas où jutilise le script ksh vous pensez que nous pouvons trouver quelque chose de similaire à pipestatus? , (pendant que je vois le pipestatus non pris en charge par ksh)
  • @yael Je ne ‘ t utiliser ksh, mais dun bref coup dœil à la page de manuel de ‘, elle ne ‘ t prend en charge $PIPESTATUS ou quelque chose de similaire. Cependant, il prend en charge loption pipefail.
  • Jai décidé dutiliser pipefail car cela me permet dobtenir le statut de la commande ayant échoué ici: LOG=$(failed_command | successful_command)

Réponse

Cette solution fonctionne sans utiliser de fonctionnalités spécifiques à bash ni de fichiers temporaires . Bonus: au final, le statut de sortie est en fait un statut de sortie et non une chaîne dans un fichier.

Situation:

someprog | filter 

vous veulent le statut de sortie de someprog et la sortie de filter.

Voici ma solution:

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

le résultat de cette construction est stdout de filter comme stdout de la construction et état de sortie de someprog comme état de sortie de la construction.


cette construction fonctionne également avec un simple groupement de commandes {...} au lieu de sous-shell (...). les sous-shell ont des implications, entre autres un coût de performance, dont nous navons pas besoin ici. lisez le manuel de fine bash pour plus de détails: 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 

Malheureusement, la grammaire bash nécessite des espaces et des points-virgules pour les accolades afin que la construction devienne beaucoup plus spacieuse.

Pour le reste de ce texte, jutiliserai la variante sous-shell.


Exemple someprog et 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 $? 

Exemple de sortie:

filtered line1 filtered line2 filtered line3 42 

Remarque: le processus enfant hérite des descripteurs de fichiers ouverts du parent. Cela signifie que someprog héritera des descripteurs de fichier ouverts 3 et 4. Si someprog écrit dans le descripteur de fichier 3, cela deviendra létat de sortie. Le statut de sortie réel sera ignoré car read ne lit quune seule fois.

Si vous craignez que votre someprog puisse écrire au descripteur de fichier 3 ou 4, il est préférable de fermer les descripteurs de fichier avant dappeler someprog.

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

Le exec 3>&- 4>&- avant someprog ferme le descripteur de fichier avant dexécuter someprog donc pour someprog ces descripteurs de fichiers nexistent tout simplement pas.

Cela peut aussi sécrire comme ceci: someprog 3>&- 4>&-


Explication étape par étape de la construction:

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

De bas en haut:

  1. Un sous-shell est créé avec un descripteur de fichier 4 redirigé vers stdout. Cela signifie que tout ce qui est imprimé dans le descripteur de fichier 4 dans le sous-shell se retrouvera comme sortie standard de la construction entière.
  2. Un tube est créé et les commandes sur la gauche (#part3) et à droite (#part2) sont exécutés. exit $xs est également la dernière commande du tube et cela signifie que la chaîne de stdin sera létat de sortie de lensemble de la construction.
  3. Un sous-shell est créé avec le fichier descripteur 3 redirigé vers stdout. Cela signifie que tout ce qui est imprimé dans le descripteur de fichier 3 dans ce sous-shell se retrouvera dans #part2 et sera à son tour le statut de sortie de lensemble de la construction.
  4. A pipe est créée et les commandes à gauche (#part5 et #part6) et à droite (filter >&4) sont exécutés. La sortie de filter est redirigée vers le descripteur de fichier 4. Dans #part1 le descripteur de fichier 4 a été redirigé vers stdout. Cela signifie que la sortie de filter est la sortie standard de la construction entière.
  5. Le statut de sortie de #part6 est affiché vers le descripteur de fichier 3. Dans #part3, le descripteur de fichier 3 a été redirigé vers #part2. Cela signifie que létat de sortie de #part6 sera létat de sortie final pour lensemble de la construction.
  6. someprog est réalisé. Létat de sortie est pris dans #part5. La sortie standard est prise par le tube dans #part4 et transmise à filter. La sortie de filter atteindra à son tour stdout comme expliqué dans #part4

Commentaires

  • Sympa. Pour la fonction, je pourrais simplement faire (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) simplifie en someprog 3>&- 4>&-.
  • Cette méthode fonctionne également sans sous-shell: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Réponse

Bien que ce ne soit pas exactement ce que vous avez demandé, vous pouvez utiliser

#!/bin/bash -o pipefail 

pour que vos tubes renvoient le dernier retour non nul.

peut-être un peu moins de codage

Edit: Exemple

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

Commentaires

  • set -o pipefail à lintérieur du script devrait être plus robuste, par exemple au cas où quelquun exécute le script via bash foo.sh.
  • Comment ça marche? avez-vous un exemple?
  • Notez que -o pipefail nest pas dans POSIX.
  • Cela ne fonctionne pas dans mon BASH 3.2.25 ( 1) -release. En haut de / tmp / ff, jai #!/bin/bash -o pipefail.Lerreur est: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: Certains environnements (y compris Linux) don ‘ t analyser les espaces sur #! lignes au-delà de la première, et ainsi cela devient /bin/bash -o pipefail /tmp/ff, au lieu des /bin/bash -o pipefail /tmp/ffgetopt (ou similaire) analyse en utilisant le optarg, qui est lélément suivant dans ARGV, comme argument de -o, donc il échoue. Si vous deviez créer un wrapper (par exemple, bash-pf qui vient de faire exec /bin/bash -o pipefail "$@", et placez-le sur , cela fonctionnerait. Voir aussi: en.wikipedia.org/wiki/Shebang_%28Unix%29

Réponse

Ce que je fais lorsque cela est possible est de transmettre le code de sortie de foo dans bar. Par exemple, si je sais que foo ne produit jamais une ligne avec juste des chiffres, alors je peux simplement clouer le code de sortie:

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

Ou si je sais que la sortie de foo ne contient jamais de ligne avec seulement .:

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

Cela peut toujours être fait sil existe un moyen dobtenir bar pour travailler sur toutes les lignes sauf la dernière, et passer la dernière ligne comme code de sortie.

Si bar est un pipeline complexe dont vous pas besoin, vous pouvez en contourner une partie en imprimant le code de sortie sur un autre descripteur de fichier.

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

Après ceci $exit_codes est généralement foo:X bar:Y, mais cela pourrait être bar:Y foo:X si bar se ferme avant de lire toutes ses entrées ou si vous êtes malchanceux. Je pense que les écritures sur des tubes jusquà 512 octets sont atomiques sur tous les unices, donc les parties foo:$? et bar:$? ne seront « pas mélangées comme tant que les chaînes de balises sont inférieures à 507 octets.

Si vous avez besoin de capturer la sortie de bar, cela devient difficile. Vous pouvez combiner les techniques ci-dessus en organisant pour que la sortie de bar ne contienne jamais une ligne qui ressemble à une indication de code de sortie, mais elle devient compliquée.

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

Et, bien sûr, il ya la simple option de en utilisant un fichier temporaire pour stocker le statut. Simple, mais pas que simple en production:

  • Sil y a plusieurs scripts exécutés simultanément, ou si le même script utilise cette méthode à plusieurs endroits, vous devez faire assurez-vous quils utilisent des noms de fichiers temporaires différents.
  • La création dun fichier temporaire en toute sécurité dans un répertoire partagé est difficile. Souvent, /tmp est le seul endroit où un script est sûr de pouvoir écrire des fichiers. Utilisez mktemp , qui nest pas POSIX mais disponible sur tous les appareils sérieux de nos jours.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Commentaires

  • Lors de lutilisation de lapproche de fichier temporaire, je préfère ajouter un piège pour EXIT qui supprime tous les fichiers temporaires de sorte quaucune poubelle ne soit laissée même si le script meurt

Answer

À partir du pipeline:

foo | bar | baz 

Voici une solution générale utilisant uniquement le shell POSIX et pas de fichiers temporaires:

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

$error_statuses contient les codes détat de tous les processus ayant échoué, dans un ordre aléatoire, avec des index pour indiquer quelle commande a émis chaque état.

# 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 

Notez les guillemets autour de $error_statuses dans mes tests; sans eux, grep ne peut « t différencier car les retours à la ligne sont forcés à des espaces.

Réponse

Si le package moreutils est installé, vous pouvez utiliser lutilitaire méspuce qui fait exactement ce que vous avez demandé.

Réponse

Je voulais donc apporter une réponse comme lesmana « s, mais je pense que la mienne est peut-être un peu plus simple et légèrement plus avantageuse pure- Solution 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. 

Je pense que cest mieux expliqué de lintérieur – command1 exécutera et imprimera sa sortie régulière sur stdout (descripteur de fichier 1) , puis une fois terminé, printf exécutera et affichera le code de sortie de la commande1 sur sa sortie standard, mais cette sortie est redirigée vers le descripteur de fichier 3.

Pendant que la commande1 est en cours dexécution, sa sortie standard est envoyée command2 (la sortie de printf ne parvient jamais à command2 car nous lenvoyons au descripteur de fichier 3 au lieu de 1, ce que je s ce que lit le tuyau).Ensuite, nous redirigeons la sortie de command2 vers le descripteur de fichier 4, afin quil reste également en dehors du descripteur de fichier 1 – car nous voulons que le descripteur de fichier 1 soit libre un peu plus tard, car nous ramènerons la sortie printf sur le descripteur de fichier 3 dans descripteur de fichier 1 – parce que cest ce que la substitution de commande (les backticks) capturera et cest ce qui sera placé dans la variable.

Le dernier peu de magie est que le premier exec 4>&1 nous lavons fait comme une commande séparée – il ouvre le descripteur de fichier 4 comme une copie de la sortie standard du shell externe. La substitution de commande capturera tout ce qui est écrit sur la sortie standard du point de vue des commandes à lintérieur – mais, puisque la sortie de command2 va au descripteur de fichier 4 en ce qui concerne la substitution de commande, la substitution de commande ne le capture pas – cependant, une fois quil est « sorti » de la substitution de commande, il va effectivement toujours au descripteur de fichier global du script 1.

(Le exec 4>&1 a être une commande distincte car de nombreux shells communs naiment pas lorsque vous essayez décrire dans un descripteur de fichier à lintérieur dune substitution de commande, qui est ouverte dans la commande « externe » qui utilise la substitution. Cest donc la manière portable la plus simple pour le faire.)

Vous pouvez le regarder de manière moins technique et plus ludique, comme si les sorties des commandes sautaient les unes sur les autres: command1 redirige vers command2, puis la sortie de printf saute sur la commande 2 pour que la commande 2 ne lattrape pas, puis la sortie de la commande 2 saute par-dessus La substitution de commande juste au moment où printf arrive juste à temps pour être capturée par la substitution de sorte quelle se retrouve dans la variable, et la sortie de command2 continue sa joyeuse façon dêtre écrite sur la sortie standard, tout comme dans un tube normal.

De plus, si je comprends bien, $? contiendra toujours le code de retour de la deuxième commande dans le tube, car les affectations de variables, les substitutions de commandes et les commandes composées sont tous effectivement transparents au code de retour de la commande à lintérieur deux, donc le statut de retour de command2 devrait être propagé – ceci, et ne pas avoir à définir une fonction supplémentaire, cest pourquoi je pense que cela pourrait être une solution un peu meilleure que celle proposée par lesmana.

Selon les mises en garde mentionnées par lesmana, il est possible que command1 finisse par utiliser les descripteurs de fichier 3 ou 4, donc pour être plus robuste, vous feriez:

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

Notez que jutilise des commandes composées dans mon exemple, mais des sous-shell (en utilisant ( ) au lieu de { } fonctionnera également, mais peut-être peut-être moins efficace.)

Les commandes héritent des descripteurs de fichiers du processus qui les lance, donc toute la deuxième ligne héritera du descripteur de fichier quatre, et la commande composée suivie de 3>&1 héritera du descripteur de fichier trois. Ainsi, 4>&- sassure que la commande composée interne nhéritera pas du descripteur de fichier quatre et que 3>&- nhéritera pas du descripteur de fichier trois, donc command1 obtient un environnement plus « plus propre » et plus standard. Vous pouvez également déplacer le 4>&- intérieur à côté du 3>&-, mais je comprends pourquoi ne pas limiter sa portée autant que possible.

Je ne sais pas à quelle fréquence les choses utilisent directement les descripteurs de fichier trois et quatre – je pense que la plupart du temps, les programmes utilisent des appels système qui renvoient des descripteurs de fichier non utilisés pour le moment, mais parfois le code écrit dans un fichier descripteur 3 directement, je suppose (je pourrais imaginer un programme vérifiant un descripteur de fichier pour voir sil est ouvert, et lutilisant si tel est le cas, ou se comportant différemment si ce nest pas le cas). Donc, ce dernier est probablement préférable de conserver à lesprit et à utiliser dans des cas généraux.

Commentaires

  • Ça a lair intéressant, mais je peux ‘ Je ne comprends pas vraiment ce que vous attendez de cette commande, et mon ordinateur peut ‘ non plus; jobtiens -bash: 3: Bad file descriptor.
  • @ G-Man Daccord, joublie toujours que bash na aucune idée de ce quil fait ‘ quand il co mes aux descripteurs de fichier, contrairement aux shells que jutilise généralement (le ash qui vient avec busybox). Je ‘ vous ferai savoir quand je pense à une solution de contournement qui rend bash heureux. En attendant, si vous ‘ avez une boîte Debian à portée de main, vous pouvez lessayer sous forme de tableau de bord, ou si vous ‘ avez busybox à portée de main peut lessayer avec busybox ash / sh.
  • @ G-Man Quant à ce que jattends de la commande, et ce quelle fait dans dautres shells, est de rediriger stdout à partir de command1 afin quil ne ‘ t être attrapé par la substitution de commande, mais une fois en dehors de la substitution de commande, il retourne fd3 vers stdout afin quil ‘ soit envoyé comme prévu à command2.Lorsque command1 se termine, le printf se déclenche et imprime son état de sortie, qui est capturé dans la variable par la substitution de commande. Détail très détaillé ici: stackoverflow.com/questions/985876/tee-and-exit-status/… Aussi , ce commentaire que vous avez lu comme sil était censé être un peu insultant?
  • Par où commencer? (1) Je suis désolé si vous vous êtes senti insulté. «Ça a lair intéressant» voulait dire sincèrement; ce serait formidable si quelque chose daussi compact que celui-là fonctionnait aussi bien que vous vous y attendiez. Au-delà, je disais simplement que je ne comprenais pas ce que votre solution était censée faire. Je travaille / joue avec Unix depuis longtemps (avant l’existence de Linux), et, si je ne comprends pas quelque chose, c’est un signal dalarme qui, peut-être, dautres personnes ne le comprendront pas non plus, et quil a besoin de plus dexplications (IMNSHO). … (Suite)
  • (suite)… Puisque vous « … aimez penser… que [vous] comprenez à peu près tout plus que la personne moyenne », vous devriez peut-être vous rappeler que lobjectif de Stack Exchange nest pas dêtre un service décriture de commandes, produisant des milliers de solutions uniques à des questions trivialement distinctes; mais plutôt pour apprendre aux gens à résoudre leurs propres problèmes. Et, à cette fin, peut-être avez-vous besoin dexpliquer suffisamment bien les choses pour quune «personne moyenne» puisse les comprendre. Regardez la réponse de lesmana pour un exemple d’excellente explication. … (Suite)

Réponse

La solution de lesmana ci-dessus peut également être effectuée sans la surcharge de démarrer des sous-processus imbriqués en utilisant plutôt { .. } (en vous rappelant que cette forme de commandes groupées doit toujours se terminer par des points-virgules). Quelque chose comme ceci:

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

Jai vérifié cette construction avec dash version 0.5.5 et bash versions 3.2.25 et 4.2.42, donc même si certains shells ne supportent pas { .. } groupement, il est toujours compatible POSIX.

Commentaires

  • Cela fonctionne très bien avec la plupart des shells I ‘ lai essayé avec, y compris NetBSD sh, pdksh, mksh, dash, bash. Cependant, je ne peux ‘ le faire fonctionner avec AT & T Ksh (93s +, 93u +) ou zsh (4.3.9, 5.2), même avec set -o pipefail en ksh ou tout nombre de wait dans les deux. Je pense que cela peut, en partie au moins, ce sera un problème danalyse pour ksh, comme si je men tenais à lutilisation de sous-shell, cela fonctionne bien, mais même avec un if pour choisir la variante de sous-shell pour ksh mais laisser les commandes composées pour dautres, cela échoue.

Réponse

Ce qui suit est conçu comme un complément à la réponse de @Patrik, au cas où vous ne seriez pas en mesure dutiliser lune des solutions courantes.

Cette réponse suppose ce qui suit:

  • Vous avez un shell qui ne connaît pas $PIPESTATUS ni set -o pipefail
  • Vous souhaitez utiliser un tube pour une exécution parallèle, donc pas de fichiers temporaires.
  • Vous ne voulez pas avoir dencombrement supplémentaire si vous interrompez le script, peut-être par une coupure de courant soudaine.
  • Cette solution devrait être relativement facile à suivre et propre à lire.
  • Vous faites vous ne voulez pas introduire de sous-shell supplémentaires.
  • Vous ne pouvez pas manipuler les descripteurs de fichiers existants, donc stdin / out / err ne doit pas être touché (comment jamais vous pouvez en introduire un nouveau temporairement)

Hypothèses supplémentaires. Vous pouvez vous débarrasser de tout, mais cela écrase trop la recette, donc ce nest pas couvert ici:

  • Tout ce que vous voulez savoir, cest que toutes les commandes du PIPE ont le code de sortie 0.
  • Vous navez pas besoin dinformations supplémentaires sur la bande latérale.
  • Votre shell attend le retour de toutes les commandes de canal.

Avant: foo | bar | baz, mais cela ne renvoie que le code de sortie de la dernière commande (baz)

Recherché: $? ne doit pas être 0 (true), si lune des commandes du tube a échoué

Après:

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 

Expliqué:

  • Un fichier temporaire est créé avec mktemp. Cela crée généralement immédiatement un fichier dans /tmp
  • Ce fichier temporaire est ensuite redirigé vers FD 9 pour lécriture et FD 8 pour la lecture
  • Puis le tempfile est immédiatement supprimé. Il reste ouvert, cependant, jusquà ce que les deux FD disparaissent.
  • Maintenant, le tube est démarré. Chaque étape sajoute à FD 9 uniquement, sil y a eu une erreur.
  • Le wait est nécessaire pour ksh, car ksh else nattend pas la fin de toutes les commandes pipe. Cependant, veuillez noter quil y a des effets secondaires indésirables si certaines tâches darrière-plan sont présentes, je lai donc commenté par défaut.Si lattente ne fait pas mal, vous pouvez la commenter.
  • Ensuite, le contenu du fichier est lu. Sil est vide (car tout a fonctionné) read renvoie false, donc true indique une erreur

Ceci peut être utilisé comme un remplacement de plugin pour une seule commande et ne nécessite que les éléments suivants:

  • FD inutilisés 9 et 8
  • Une seule variable denvironnement pour contenir le nom du fichier temporaire
  • Et ceci La recette peut être adaptée à pratiquement nimporte quel shell qui permet la redirection dE / S
  • De plus, elle est assez indépendante de la plate-forme et na pas besoin de choses comme /proc/fd/N

BOGUES:

Ce script a un bogue au cas où /tmp manquer despace. Si vous avez également besoin de protection contre ce cas artificiel, vous pouvez le faire comme suit, mais cela présente linconvénient que le nombre de 0 dans 000 dépend du nombre de commandes dans lepipe, donc cest un peu plus compliqué:

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" 

Notes de portabilité:

  • ksh et les shells similaires qui nattendent que la dernière commande pipe ont besoin du wait décommenté

  • Le dernier exemple utilise printf "%1s" "$?" au lieu de echo -n "$?" car cest plus portable. Toutes les plates-formes n’interprètent pas correctement -n.

  • printf "$?" le ferait également, cependant printf "%1s" attrape quelques cas de coin au cas où vous exécuteriez le script sur une plate-forme vraiment cassée. (Lire: si vous programmez en paranoia_mode=extreme.)

  • FD 8 et FD 9 peuvent être plus élevés sur les plates-formes qui prennent en charge plusieurs chiffres. AFAIR, un shell conforme POSIX na besoin que de prendre en charge les chiffres uniques.

  • A été testé avec Debian 8.2 sh, bash, ksh, ash, sash et même csh

Réponse

Cest portable, cest-à-dire fonctionne avec nimporte quel shell compatible POSIX, ne nécessite pas que le répertoire courant soit accessible en écriture et permet à plusieurs scripts utilisant la même astuce de sexécuter simultanément.

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

Modifier: voici une version plus forte suite aux commentaires de Gilles:

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

Edit2: et voici une variante un peu plus légère suite au commentaire dubiousjim:

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

Commentaires

  • Cela ne ‘ t fonctionne pour plusieurs raisons. 1. Le fichier temporaire peut être lu avant dêtre ‘ écrit. 2. La création dun fichier temporaire dans un répertoire partagé avec un nom prévisible nest pas sécurisée (DoS trivial, course de liens symboliques). 3. Si le même script utilise cette astuce plusieurs fois, il ‘ utilisera toujours le même nom de fichier. Pour résoudre 1, lisez le fichier une fois le pipeline terminé. Pour résoudre 2 et 3, utilisez un fichier temporaire avec un nom généré aléatoirement ou dans un répertoire privé.
  • +1 Eh bien, le $ {PIPESTATUS [0]} est plus simple mais lidée de base ici fonctionne si on connaît les problèmes évoqués par Gilles.
  • Vous pouvez enregistrer quelques sous-shell: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Je suis daccord que ‘ est plus facile avec Bash mais dans certains contextes, savoir comment éviter Bash en vaut la peine.

Réponse

Avec un peu de précaution, cela devrait fonctionner:

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

Commentaires

  • Que diriez-vous de nettoyer comme jlliagre? Ne ‘ pas laisser un fichier appelé foo-status?
  • @Johan: Si vous préférez ma suggestion, ne ‘ t hésiter à voter;) En plus de ne pas laisser de fichier, il a lavantage de permettre à plusieurs processus de lexécuter simultanément et le répertoire courant na pas besoin dêtre accessible en écriture.

Réponse

Le bloc « if » suivant ne fonctionnera que si « command » réussit:

if command; then # ... fi 

Plus précisément, vous pouvez exécuter quelque chose comme ceci:

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

Qui exécutera haconf -makerw et stockez ses stdout et stderr dans « $ haconf_out ». Si la valeur renvoyée par haconf est vraie, alors le bloc « if » sera exécuté et grep lira « $ haconf_out », en essayant pour le comparer à « Cluster déjà accessible en écriture ».

Notez que les tuyaux se nettoient automatiquement; avec la redirection, vous « devrez faire attention à supprimer » $ haconf_out « une fois terminé.

Pas aussi élégant que pipefail, mais une alternative légitime si cette fonctionnalité nest pas à portée de main.

Réponse

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éponse

(Avec bash au moins) combiné avec set -e on peut utiliser subshell pour émuler explicitement pipefail et sortir en cas derreur de pipe

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

Donc, si foo échoue pour une raison quelconque – le reste du programme ne sera pas exécuté et le script se terminera avec le code derreur correspondant. (Cela suppose que foo imprime sa propre erreur, ce qui est suffisant pour comprendre la raison de léchec)

Réponse

EDIT : Cette réponse est fausse, mais intéressante, je vais donc la laisser pour lavenir référence.


Lajout dun ! à la commande inverse le code de retour.

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

Commentaires

  • Je pense que cela na aucun rapport. Dans votre exemple, je veux connaître le code de sortie de ls, ne pas inverser le code de sortie de bogus_command
  • Je suggère de revenir sur cette réponse.
  • Eh bien, il semble que je ‘ suis un idiot. Je ‘ jai utilisé ceci dans un script avant de penser quil faisait ce que lOP voulait. Heureusement que je nai ‘ pas lutiliser pour quelque chose dimportant

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *