Exécutez ce qui précède dans bashou zshet vous obtiendrez les mêmes résultats. un seul retval_bashet retval_zshsera réglé. L'autre sera vide. Cela permettrait à une fonction de se terminer par return $retval_bash $retval_zsh(notez le manque de guillemets!).
Et pipestatusen zsh. Malheureusement, d'autres coquilles n'ont pas cette fonctionnalité.
Gilles
8
Remarque: Les tableaux dans zsh commencent de façon contre-intuitive à l'index 1, donc c'est echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm
5
Vous pouvez vérifier l'ensemble du pipeline comme if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
suit
7
@JanHudec: Peut-être devriez-vous lire les cinq premiers mots de ma réponse. Indiquez également avec bonté où la question demandait une réponse POSIX uniquement.
camh
12
@JanHudec: Ce n'était pas non plus étiqueté POSIX. Pourquoi supposez-vous que la réponse doit être POSIX? Ce n'était pas précisé, alors j'ai fourni une réponse qualifiée. Ma réponse n’est pas fausse, de plus, il existe de nombreuses réponses pour traiter d’autres cas.
camh
238
Il y a 3 façons courantes de le faire:
Pipefail
La première consiste à définir l’ pipefailoption ( ksh, zshou bash). C’est le plus simple. En général, l’état $?de sortie est défini sur le code de sortie du dernier programme à quitter non nul (ou zéro si tous les éléments sont sortis avec succès).
Bash a également une variable de tableau appelée $PIPESTATUS( $pipestatusin zsh) qui contient l’état de sortie de tous les programmes du dernier pipeline.
@Patrick la solution pipestatus fonctionne sur bash, juste plus si vous utilisez un script ksh vous pensez que nous pouvons trouver quelque chose de similaire à pipestatus? , (depuis que je vois le statut de tuyauterie non supporté par ksh)
2
2
@yael, je ne l'utilise pas ksh, mais d'un simple coup d'œil à sa page de manuel, elle ne prend pas en charge $PIPESTATUSou quelque chose de similaire. Il supporte cependant l' pipefailoption.
Patrick le
1
J'ai décidé d'y aller avec pipefail car cela me permet d'obtenir le statut de la commande ayant échoué ici:LOG=$(failed_command | successful_command)
vmrob le
51
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 voulez le statut de sortie someproget la sortie filter.
le résultat de cette construction est stdout from filteras stdout de la construction et status de someprogexit from exit statut de la construction.
cette construction fonctionne également avec un simple regroupement de commandes {...}au lieu de sous-shell (...). Les sous-coques ont des implications, entre autres un coût de performance, dont nous n’avons pas besoin ici. lisez le manuel de Bash pour plus de détails: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
Malheureusement, la grammaire bash nécessite des espaces et des points-virgules pour les accolades afin que la construction devienne beaucoup plus spacieuse.
Pour la suite de ce texte, je vais utiliser la variante du sous-shell.
Remarque: le processus enfant hérite des descripteurs de fichier ouverts du parent. Cela signifie someprogqu’il héritera des descripteurs de fichier ouverts 3 et 4. Si l’ someprogécriture dans le descripteur de fichier 3 devient le statut de sortie. Le statut de sortie réel sera ignoré car il readne lit qu'une fois.
Si vous craignez d' someprogécrire dans le descripteur de fichier 3 ou 4, il est préférable de fermer les descripteurs de fichier avant d'appeler someprog.
L' exec 3>&- 4>&-avant someprogferme le descripteur de fichier avant de l'exécuter, someprogil someprogn'existe donc tout simplement pas pour ces descripteurs de fichier.
Cela peut aussi être écrit comme ceci: someprog 3>&- 4>&-
Un sous-shell est créé avec le 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 finira par devenir la sortie standard de la construction entière.
Un canal est créé et les commandes de gauche ( #part3) et de droite ( #part2) sont exécutées. exit $xsest également la dernière commande du tube et cela signifie que la chaîne de stdin sera le statut de sortie de la construction entière.
Un sous-shell est créé avec le descripteur de fichier 3 redirigé vers stdout. Cela signifie que tout ce qui est imprimé dans le descripteur de fichier 3 de ce sous-shell finira par #part2devenir le statut de sortie de la construction entière.
Un pipe est créé et les commandes à gauche ( #part5et #part6) et à droite ( filter >&4) sont exécutées. La sortie de filterest redirigée vers le descripteur de fichier 4. Dans #part1le descripteur de fichier, 4 a été redirigé vers la sortie standard. Cela signifie que la sortie de filterest la sortie standard de la construction entière.
Le statut de sortie de #part6est imprimé dans le descripteur de fichier 3. Dans #part3le descripteur de fichier 3 a été redirigé vers #part2. Cela signifie que le statut de sortie de #part6sera le statut de sortie final de la construction entière.
someprogest exécuté. Le statut de sortie est pris en compte #part5. La sortie standard est prise par le tuyau #part4et transmise à filter. La sortie de filteraboutira à son tour à stdout comme expliqué dans#part4
set -o pipefaill'intérieur du script devrait être plus robuste, par exemple au cas où quelqu'un l'exécuterait via bash foo.sh.
maxschlepzig
Comment ça marche? as-tu un exemple?
Johan
2
Notez que ce -o pipefailn'est pas dans POSIX.
scy
2
Cela ne fonctionne pas dans ma version 3.2.25 (1) de BASH. Au sommet de / tmp / ff j'ai #!/bin/bash -o pipefail. L'erreur est:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez
2
@FelipeAlvarez: Certains environnements (y compris Linux) n'analysent pas les espaces sur les #!lignes au-delà de la première, de sorte que cela devient /bin/bash-o pipefail/tmp/ff, au lieu de l' analyse nécessaire /bin/bash-opipefail/tmp/ff- getopt(ou similaire), l'utilisation de optarg, l'élément suivant dans ARGVl'argument to -o, donc ça échoue. Si vous deviez créer un wrapper (par exemple, bash-pfcela vient de se produire exec /bin/bash -o pipefail "$@", et le mettrait en #!ligne, cela fonctionnerait. Voir aussi: en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes
22
Ce que je fais lorsque cela est possible est d'alimenter le code de sortie fooen bar. Par exemple, si je sais que cela foone produit jamais une ligne comportant uniquement des chiffres, je peux simplement ajouter le code de sortie:
Cela peut toujours être fait s'il existe un moyen de bartravailler sur toutes les lignes sauf la dernière et de passer à la dernière ligne comme code de sortie.
S'il bars'agit d'un pipeline complexe dont vous n'avez pas besoin en sortie, vous pouvez en contourner une partie en imprimant le code de sortie sur un descripteur de fichier différent.
exit_codes=$({{ foo; echo foo:"$?">&3;}|{ bar >/dev/null; echo bar:"$?">&3;}}3>&1)
Après cela $exit_codesest généralement foo:X bar:Y, mais cela pourrait être bar:Y foo:Xsi vous barquittez avant de lire toutes ses entrées ou si vous êtes malchanceux. Je pense aux tuyaux écrit jusqu'à 512 octets sont atomiques sur tous unix, de sorte que les foo:$?et bar:$?parties ne seront pas entremêlées tant que les chaînes de balise sont sous 507 octets.
Si vous devez capturer la sortie bar, cela devient difficile. Vous pouvez combiner les techniques ci-dessus en faisant en sorte que la sortie de barne jamais contenir une ligne qui ressemble à une indication de code de sortie, mais cela devient difficile.
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 existe la possibilité simple d’ utiliser un fichier temporaire pour stocker le statut. Simple, mais pas si simple en production:
Si plusieurs scripts s'exécutent simultanément ou si le même script utilise cette méthode à plusieurs endroits, vous devez vous assurer qu'ils utilisent des noms de fichiers temporaires différents.
Il est difficile de créer un fichier temporaire en toute sécurité dans un répertoire partagé. C'est souvent /tmple seul endroit où un script est sûr de pouvoir écrire des fichiers. Utilisez mktemp, ce qui n’est pas POSIX mais disponible sur tous les ordinateurs sérieux de nos jours.
Lorsque j'utilise l'approche de fichier temporaire, je préfère ajouter une interruption pour EXIT qui supprime tous les fichiers temporaires de sorte qu'aucune faille ne reste, même si le script meurt
miracle173
17
À partir du pipeline:
foo | bar | baz
Voici une solution générale utilisant uniquement un shell POSIX et aucun fichier temporaire:
$error_statuses contient les codes d'état de tous les processus en échec, 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 citations autour $error_statusesde mes tests; sans eux, il grepest impossible de faire la différence car les nouvelles lignes sont contraintes aux espaces.
Je voulais donc apporter une réponse semblable à celle de Lesmana, mais je pense que la mienne est peut-être une solution un peu plus simple et légèrement plus avantageuse de pure 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 ceci est mieux expliqué de l'intérieur - Command1 exécutera et imprimera sa sortie standard sur stdout (descripteur de fichier 1), puis une fois terminé, printf exécutera et imprimera le code de sortie de command1 sur sa sortie standard, mais ce dernier sera redirigé vers descripteur de fichier 3.
Pendant que commande1 est en cours d'exécution, son stdout est redirigé vers commande2 (la sortie de printf ne le fait jamais sur commande2 car nous l'envoyons dans le descripteur de fichier 3 au lieu de 1, ce qui est lu par le canal). Ensuite, nous redirigeons la sortie de command2 vers le descripteur de fichier 4, de sorte qu'il 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 de printf sur le descripteur de fichier 3 dans le descripteur de fichier. 1 - parce que c'est ce que la substitution de commande (les backticks) va capturer et c'est ce qui sera placé dans la variable.
La magie finale est que exec 4>&1nous avons d' abord créé une commande distincte: elle ouvre le descripteur de fichier 4 en tant que copie de la sortie standard du shell externe. La substitution de commande capturera tout ce qui est écrit en sortie standard du point de vue des commandes qu'il contient - mais, comme la sortie de command2 est dirigée vers le descripteur de fichier 4 en ce qui concerne la substitution de commande, la substitution de commande ne le capture pas - cependant, une fois qu'il est "sorti" de la substitution de commande, il reste dans le descripteur de fichier général du script 1.
(Il exec 4>&1doit s'agir d'une commande séparée, car de nombreux shells courants ne l'aiment pas lorsque vous essayez d'écrire dans un descripteur de fichier à l'intérieur d'une substitution de commande, qui est ouverte dans la commande "external" qui utilise la substitution. moyen portable le plus simple de le faire.)
Vous pouvez le regarder de manière moins technique et plus ludique, comme si les sorties des commandes se superposaient: command1 passe à command2, puis la sortie de printf saute par-dessus la commande 2 de sorte que commande2 ne l'attrape pas, puis la sortie de la commande 2 saute par-dessus la substitution de commande juste au moment où printf atterrit juste à temps pour être capturé par la substitution, de sorte qu'il se retrouve dans la variable, et la sortie de la commande2 continue à être joyeusement écrite sur la sortie standard, comme dans un tuyau normal.
De plus, si j'ai bien compris, il $?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 toutes transparentes pour le code de retour de la commande qu'elles contiennent. command2 devrait être propagé - ceci, et ne pas avoir à définir une fonction supplémentaire, est la raison pour laquelle je pense que cela pourrait être une solution légèrement meilleure que celle proposée par lesmana.
Selon les mises en garde lesmana, il est possible que command1 finisse par utiliser les descripteurs de fichier 3 ou 4, aussi, pour être plus robuste, vous feriez:
Notez que j’utilise des commandes composées dans mon exemple, mais des sous-shell (utiliser ( )au lieu de { }fonctionnera également, même s’il est peut-être moins efficace.)
Les commandes héritent des descripteurs de fichier du processus qui les lance, de sorte que la deuxième ligne entière hérite du descripteur de fichier quatre et que la commande composée suivie de 3>&1hérite du descripteur de fichier trois. Ainsi, le groupe 4>&-s'assure que la commande composée interne n'héritera pas du descripteur de fichier quatre et 3>&-n'héritera pas du descripteur de fichier trois. Ainsi, commande1 obtient un environnement plus «propre», plus standard. Vous pouvez également déplacer l’intérieur à 4>&-côté du 3>&-, mais j’imagine pourquoi ne pas simplement en limiter le plus possible la portée.
Je ne suis pas sûr de la fréquence à laquelle les choses utilisent les descripteurs de fichier trois et quatre directement. 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 que le code écrit parfois directement dans le descripteur de fichier 3. devinez (je pourrais imaginer un programme vérifiant un descripteur de fichier pour voir s'il est ouvert, et l'utiliser si c'est le cas, ou se comporter différemment si ce n'est pas le cas). Donc, ce dernier est probablement préférable de garder à l'esprit et à utiliser pour les cas à usage général.
Cela semble intéressant, mais je ne peux pas vraiment comprendre ce que vous attendez de cette commande, et mon ordinateur ne le peut pas non plus; Je reçois -bash: 3: Bad file descriptor.
G-Man
@ G-Man Bien, j'oublie toujours que bash n'a aucune idée de ce qu'il fait lorsqu'il s'agit de descripteurs de fichiers, contrairement aux shells que j'utilise généralement (les cendres de busybox). Je vous ferai savoir quand je penserai à une solution de contournement qui rend Bash heureux. En attendant, si vous avez une boîte Debian à portée de main, vous pouvez l’essayer au tiret, ou si vous avez une boîte occupée à portée de main, vous pouvez l’essayer avec busybox ash / sh.
mtraceur
@ G-Man En ce qui concerne ce que j'attend de la commande et ce qu'elle fait dans les autres shells, redirige stdout à partir de command1 pour ne pas être intercepté par la substitution de commande, mais une fois en dehors de la substitution de commande, elle passe fd3 retourne à stdout afin qu'il soit acheminé comme prévu par command2. Lorsque commande1 se ferme, printf se déclenche et affiche son état de sortie, qui est capturé dans la variable par la substitution de commande. Ventilation très détaillée ici: stackoverflow.com/questions/985876/tee-and-exit-status/… De plus, votre commentaire se lit-il comme s'il était censé être un peu insultant?
mtraceur
Où vais-je commencer? (1) Je suis désolé si vous vous êtes senti insulté. «Ça a l'air intéressant» était sérieux. ce serait génial si quelque chose d'aussi compact que cela fonctionnait aussi bien que prévu. Au-delà de cela, je disais simplement que je ne comprenais pas ce que votre solution était censée faire. Je travaille / joue avec Unix depuis longtemps (avant même que Linux n’existe), et si je ne comprends pas quelque chose, c’est un drapeau rouge qui peut-être, d’ autres personnes ne le comprendront pas non plus, et cela a besoin de plus d'explications (IMNSHO). … (Suite)
G-Man le
(Suite)… Puisque vous «aimez penser… que vous comprenez à peu près tout plus que la personne moyenne», vous devriez peut-être vous rappeler que l'objectif de Stack Exchange n'est pas d'être un service de rédaction de commandes, des milliers de solutions uniques à des questions trivialement distinctes; mais plutôt d'apprendre aux gens comment résoudre leurs propres problèmes. Et, à cette fin, vous devrez peut-être expliquer suffisamment les choses pour qu'une "personne moyenne" puisse les comprendre. Regardez la réponse de Lesmana pour un exemple d'une excellente explication. … (Suite)
G-Man le
11
Si vous avez le paquet moreutils installé, vous pouvez utiliser l’ utilitaire faux - tuyau qui fait exactement ce que vous avez demandé.
La solution de lesmana ci-dessus peut également être réalisée sans la surcharge de démarrer des sous-processus imbriqués en utilisant à la { .. }place (en se rappelant que cette forme de commandes groupées doit toujours se terminer par des points-virgules). Quelque chose comme ça:
J'ai vérifié cette construction avec la version de tableau de bord 0.5.5 et les versions 3.2.25 et 4.2.42 de Bash. Par conséquent, même si certains shells ne prennent pas en charge le { .. }groupement, ils sont toujours compatibles POSIX.
Cela fonctionne très bien avec la plupart des shell avec lesquels j'ai essayé, y compris NetBSD sh, pdksh, mksh, dash, bash. Cependant, je ne peux pas le faire fonctionner avec AT & T Ksh (93s +, 93u +) ou zsh (4.3.9, 5.2), même avec set -o pipefailin ksh ou un nombre quelconque de waitcommandes saupoudrées . Je pense que cela peut, au moins en partie, être un problème d’analyse syntaxique pour ksh, comme si je m’en tenais à l’utilisation de subshells, alors cela fonctionnerait bien, mais même avec un ifchoix de la variante de sous-shell pour ksh mais en laissant les commandes composées pour d’autres, cela échoue .
Greg A. Woods
4
C'est portable, c'est-à-dire qu'il fonctionne avec n'importe quel shell compatible POSIX, ne nécessite pas que le répertoire actuel soit accessible en écriture et permet à plusieurs scripts utilisant la même astuce de s'exécuter simultanément.
Cela ne fonctionne pas pour plusieurs raisons. 1. Le fichier temporaire peut être lu avant d'être écrit. 2. La création d'un fichier temporaire dans un répertoire partagé avec un nom prévisible n'est pas sécurisée (DoS trivial, race de lien symbolique). 3. Si le même script utilise cette astuce plusieurs fois, il utilisera toujours le même nom de fichier. Pour résoudre le problème 1, lisez le fichier une fois le pipeline terminé. Pour résoudre les problèmes 2 et 3, utilisez un fichier temporaire avec un nom généré de manière aléatoire ou dans un répertoire privé.
Gilles
+1 Bien le $ {PIPESTATUS [0]} est plus facile, mais l'idée de base ici fonctionne si on connaît les problèmes mentionnés par Gilles.
Johan
Vous pouvez économiser quelques sous - couches: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Je suis d'accord que c'est plus facile avec Bash, mais dans certains contextes, savoir comment éviter Bash en vaut la peine.
dubiousjim
4
Ce qui suit est considéré comme un complément à la réponse de @Patrik, au cas où vous ne pourriez pas utiliser l’une des solutions courantes.
Cette réponse suppose que:
Vous avez une coquille qui ne sait $PIPESTATUSni deset -o pipefail
Vous voulez utiliser un canal pour une exécution parallèle, donc pas de fichiers temporaires.
Si vous interrompez le script, vous ne souhaitez peut-être pas créer de fouillis supplémentaire en raison d'une panne de courant soudaine.
Cette solution devrait être relativement facile à suivre et à lire.
Vous ne voulez pas introduire de sous-shell supplémentaires.
Vous ne pouvez pas manipuler les descripteurs de fichiers existants, vous ne devez donc pas toucher à stdin / out / err (vous pouvez toutefois en introduire temporairement de nouveaux)
Hypothèses supplémentaires. Vous pouvez vous débarrasser de tout, mais cette recette est trop lourde, elle n'est donc pas couverte ici:
Tout ce que vous voulez savoir, c'est que toutes les commandes du PIPE ont le code de sortie 0.
Vous n'avez pas besoin d'informations supplémentaires sur la bande latérale.
Votre shell attend que toutes les commandes de canal soient renvoyées.
Avant:, foo | bar | bazcependant, cela ne retourne que le code de sortie de la dernière commande ( baz)
Wanted: $?ne doit pas être 0(true), si l'une des commandes du canal 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
A 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 écriture et FD 8 pour lecture.
Ensuite, le fichier temporaire est immédiatement supprimé. Il reste ouvert jusqu'à ce que les deux FD disparaissent.
Maintenant, le tuyau est démarré. Chaque étape n’ajoute que dans FD 9, s’il ya eu une erreur.
La commande waitest nécessaire pour ksh, car kshsinon n'attend pas que toutes les commandes de canal soient terminées. Toutefois, veuillez noter qu'il existe des effets indésirables si certaines tâches d'arrière-plan sont présentes. Je l'ai donc commenté par défaut. Si l'attente ne fait pas mal, vous pouvez le commenter.
Ensuite, le contenu du fichier est lu. Si elle est vide (parce que tous ont travaillé) readretours false, donc trueindique une erreur
Ceci peut être utilisé comme remplacement de plugin pour une seule commande et n'a besoin que de ce qui suit:
FD inutilisés 9 et 8
Une seule variable d'environnement pour contenir le nom du fichier temporaire
Et cette recette peut être adaptée à n'importe quel shell permettant la redirection IO
En outre, il est assez agnostique sur la plate-forme et n'a pas besoin de choses comme /proc/fd/N
Bogues:
Ce script a un bug dans le cas où l' /tmpespace est insuffisant. Si vous avez besoin de protection contre ce cas artificiel, aussi, vous pouvez le faire comme suit, mais cela présente l'inconvénient, que le nombre d' 0en 000fonction du nombre de commandes dans le tuyau, il est donc un peu plus compliqué:
kshet des coquilles similaires qui n'attendent que la dernière commande de tuyau ont besoin du non waitcommenté
Le dernier exemple utilise printf "%1s" "$?"au lieu de echo -n "$?"parce que c'est plus portable. Toutes les plateformes n'interprètent pas -ncorrectement.
printf "$?"le ferait aussi bien, cependant printf "%1s"attrape certains cas au cas où vous exécutez le script sur une plate-forme vraiment cassée. (Lire: si vous programmez paranoia_mode=extreme.)
Les formats FD 8 et FD 9 peuvent être supérieurs sur les plates-formes prenant en charge plusieurs chiffres. AFAIR, un shell conforme à POSIX n'a besoin que de prendre en charge les chiffres uniques.
A été testé avec Debian 8.2 sh, bash, ksh, ash, sashet mêmecsh
Que diriez-vous de nettoyer comme jlliagre? Ne laissez-vous pas un fichier appelé foo-status?
Johan
@Johan: Si vous préférez ma suggestion, n'hésitez pas à la voter;) En plus de ne pas laisser de fichier, cela présente l'avantage de permettre à plusieurs processus de l'exécuter simultanément et que le répertoire en cours ne doit pas nécessairement être accessible en écriture.
jlliagre
2
Le bloc 'if' suivant ne sera exécuté que si 'commande' a réussi:
if command;then# ...fi
Plus précisément, vous pouvez exécuter quelque chose comme ceci:
Qui va exécuter haconf -makerwet stocker son stdout et stderr dans "$ haconf_out". Si la valeur renvoyée haconfest true, le bloc 'if' sera exécuté et grepindiquera "$ haconf_out", en essayant de le faire correspondre à "Le cluster est déjà accessible en écriture".
Notez que les tuyaux se nettoient automatiquement; avec la redirection, vous devrez prendre soin de supprimer "$ haconf_out" lorsque vous aurez terminé.
Pas aussi élégant que pipefail, mais une alternative légitime si cette fonctionnalité n’est pas à la portée de la main.
(Avec bash au moins) combiné avec set -eon peut utiliser un sous-shell pour émuler explicitement pipefail et sortir en cas d'erreur
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 d'erreur correspondant. (Cela suppose que soit fooimprimée sa propre erreur, ce qui est suffisant pour comprendre la raison de l'échec)
# =========================================================== ## 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.# =========================================================== #
Je pense que cela n'a aucun rapport. Dans votre exemple, je souhaite connaître le code de sortie de ls- pas inverser le code de sortie debogus_command
Michael Mrozek
2
Je suggère de retirer cette réponse.
maxschlepzig
3
Eh bien, il semble que je sois un idiot. J'ai en fait utilisé cela dans un script avant de penser que c'était ce que le PO voulait. C'est une bonne chose que je ne l'utilise pas pour quelque chose d'important
Réponses:
Si vous utilisez
bash
, vous pouvez utiliser laPIPESTATUS
variable de tableau pour obtenir le statut de sortie de chaque élément du pipeline.Si vous utilisez
zsh
, ils s'appellent tableaupipestatus
(la casse compte!) Et les indices de tableau commencent à un:Pour les combiner dans une fonction de manière à ne pas perdre les valeurs:
Exécutez ce qui précède dans
bash
ouzsh
et vous obtiendrez les mêmes résultats. un seulretval_bash
etretval_zsh
sera réglé. L'autre sera vide. Cela permettrait à une fonction de se terminer parreturn $retval_bash $retval_zsh
(notez le manque de guillemets!).la source
pipestatus
en zsh. Malheureusement, d'autres coquilles n'ont pas cette fonctionnalité.echo "$pipestatus[1]" "$pipestatus[2]"
.if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Il y a 3 façons courantes de le faire:
Pipefail
La première consiste à définir l’
pipefail
option (ksh
,zsh
oubash
). C’est le plus simple. En général, l’état$?
de sortie est défini sur le code de sortie du dernier programme à quitter non nul (ou zéro si tous les éléments sont sortis avec succès).$ PIPESTATUS
Bash a également une variable de tableau appelée
$PIPESTATUS
($pipestatus
inzsh
) qui contient l’état de sortie de tous les programmes du dernier pipeline.Vous pouvez utiliser l'exemple de la 3ème commande pour obtenir la valeur spécifique dans le pipeline dont vous avez besoin.
Exécutions séparées
C'est la plus lourde des solutions. Exécutez chaque commande séparément et capturez le statut
la source
ksh
, mais d'un simple coup d'œil à sa page de manuel, elle ne prend pas en charge$PIPESTATUS
ou quelque chose de similaire. Il supporte cependant l'pipefail
option.LOG=$(failed_command | successful_command)
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:
vous voulez le statut de sortie
someprog
et la sortiefilter
.Voici ma solution:
le résultat de cette construction est stdout from
filter
as stdout de la construction et status desomeprog
exit from exit statut de la construction.cette construction fonctionne également avec un simple regroupement de commandes
{...}
au lieu de sous-shell(...)
. Les sous-coques ont des implications, entre autres un coût de performance, dont nous n’avons pas besoin ici. lisez le manuel de Bash pour plus de détails: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.htmlMalheureusement, la grammaire bash nécessite des espaces et des points-virgules pour les accolades afin que la construction devienne beaucoup plus spacieuse.
Pour la suite de ce texte, je vais utiliser la variante du sous-shell.
Exemple
someprog
etfilter
:Exemple de sortie:
Remarque: le processus enfant hérite des descripteurs de fichier ouverts du parent. Cela signifie
someprog
qu’il héritera des descripteurs de fichier ouverts 3 et 4. Si l’someprog
écriture dans le descripteur de fichier 3 devient le statut de sortie. Le statut de sortie réel sera ignoré car ilread
ne lit qu'une fois.Si vous craignez d'
someprog
écrire dans le descripteur de fichier 3 ou 4, il est préférable de fermer les descripteurs de fichier avant d'appelersomeprog
.L'
exec 3>&- 4>&-
avantsomeprog
ferme le descripteur de fichier avant de l'exécuter,someprog
ilsomeprog
n'existe donc tout simplement pas pour ces descripteurs de fichier.Cela peut aussi être écrit comme ceci:
someprog 3>&- 4>&-
Explication pas à pas de la construction:
De bas en haut:
#part3
) et de droite (#part2
) sont exécutées.exit $xs
est également la dernière commande du tube et cela signifie que la chaîne de stdin sera le statut de sortie de la construction entière.#part2
devenir le statut de sortie de la construction entière.#part5
et#part6
) et à droite (filter >&4
) sont exécutées. La sortie defilter
est redirigée vers le descripteur de fichier 4. Dans#part1
le descripteur de fichier, 4 a été redirigé vers la sortie standard. Cela signifie que la sortie defilter
est la sortie standard de la construction entière.#part6
est imprimé dans le descripteur de fichier 3. Dans#part3
le descripteur de fichier 3 a été redirigé vers#part2
. Cela signifie que le statut de sortie de#part6
sera le statut de sortie final de la construction entière.someprog
est exécuté. Le statut de sortie est pris en compte#part5
. La sortie standard est prise par le tuyau#part4
et transmise àfilter
. La sortie defilter
aboutira à son tour à stdout comme expliqué dans#part4
la source
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
simplifie àsomeprog 3>&- 4>&-
.{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Bien que pas exactement ce que vous avez demandé, vous pouvez utiliser
afin que vos tuyaux renvoient le dernier retour non nul.
pourrait être un peu moins codant
Edit: Exemple
la source
set -o pipefail
l'intérieur du script devrait être plus robuste, par exemple au cas où quelqu'un l'exécuterait viabash foo.sh
.-o pipefail
n'est pas dans POSIX.#!/bin/bash -o pipefail
. L'erreur est:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
lignes au-delà de la première, de sorte que cela devient/bin/bash
-o pipefail
/tmp/ff
, au lieu de l' analyse nécessaire/bin/bash
-o
pipefail
/tmp/ff
-getopt
(ou similaire), l'utilisation deoptarg
, l'élément suivant dansARGV
l'argument to-o
, donc ça échoue. Si vous deviez créer un wrapper (par exemple,bash-pf
cela vient de se produireexec /bin/bash -o pipefail "$@"
, et le mettrait en#!
ligne, cela fonctionnerait. Voir aussi: en.wikipedia.org/wiki/Shebang_%28Unix%29Ce que je fais lorsque cela est possible est d'alimenter le code de sortie
foo
enbar
. Par exemple, si je sais que celafoo
ne produit jamais une ligne comportant uniquement des chiffres, je peux simplement ajouter le code de sortie:Ou si je sais que la sortie de
foo
ne contient jamais une ligne avec juste.
:Cela peut toujours être fait s'il existe un moyen de
bar
travailler sur toutes les lignes sauf la dernière et de passer à la dernière ligne comme code de sortie.S'il
bar
s'agit d'un pipeline complexe dont vous n'avez pas besoin en sortie, vous pouvez en contourner une partie en imprimant le code de sortie sur un descripteur de fichier différent.Après cela
$exit_codes
est généralementfoo:X bar:Y
, mais cela pourrait êtrebar:Y foo:X
si vousbar
quittez avant de lire toutes ses entrées ou si vous êtes malchanceux. Je pense aux tuyaux écrit jusqu'à 512 octets sont atomiques sur tous unix, de sorte que lesfoo:$?
etbar:$?
parties ne seront pas entremêlées tant que les chaînes de balise sont sous 507 octets.Si vous devez capturer la sortie
bar
, cela devient difficile. Vous pouvez combiner les techniques ci-dessus en faisant en sorte que la sortie debar
ne jamais contenir une ligne qui ressemble à une indication de code de sortie, mais cela devient difficile.Et, bien sûr, il existe la possibilité simple d’ utiliser un fichier temporaire pour stocker le statut. Simple, mais pas si simple en production:
/tmp
le seul endroit où un script est sûr de pouvoir écrire des fichiers. Utilisezmktemp
, ce qui n’est pas POSIX mais disponible sur tous les ordinateurs sérieux de nos jours.la source
À partir du pipeline:
Voici une solution générale utilisant uniquement un shell POSIX et aucun fichier temporaire:
$error_statuses
contient les codes d'état de tous les processus en échec, dans un ordre aléatoire, avec des index pour indiquer quelle commande a émis chaque état.Notez les citations autour
$error_statuses
de mes tests; sans eux, ilgrep
est impossible de faire la différence car les nouvelles lignes sont contraintes aux espaces.la source
Je voulais donc apporter une réponse semblable à celle de Lesmana, mais je pense que la mienne est peut-être une solution un peu plus simple et légèrement plus avantageuse de pure Bourne-shell:
Je pense que ceci est mieux expliqué de l'intérieur - Command1 exécutera et imprimera sa sortie standard sur stdout (descripteur de fichier 1), puis une fois terminé, printf exécutera et imprimera le code de sortie de command1 sur sa sortie standard, mais ce dernier sera redirigé vers descripteur de fichier 3.
Pendant que commande1 est en cours d'exécution, son stdout est redirigé vers commande2 (la sortie de printf ne le fait jamais sur commande2 car nous l'envoyons dans le descripteur de fichier 3 au lieu de 1, ce qui est lu par le canal). Ensuite, nous redirigeons la sortie de command2 vers le descripteur de fichier 4, de sorte qu'il 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 de printf sur le descripteur de fichier 3 dans le descripteur de fichier. 1 - parce que c'est ce que la substitution de commande (les backticks) va capturer et c'est ce qui sera placé dans la variable.
La magie finale est que
exec 4>&1
nous avons d' abord créé une commande distincte: elle ouvre le descripteur de fichier 4 en tant que copie de la sortie standard du shell externe. La substitution de commande capturera tout ce qui est écrit en sortie standard du point de vue des commandes qu'il contient - mais, comme la sortie de command2 est dirigée vers le descripteur de fichier 4 en ce qui concerne la substitution de commande, la substitution de commande ne le capture pas - cependant, une fois qu'il est "sorti" de la substitution de commande, il reste dans le descripteur de fichier général du script 1.(Il
exec 4>&1
doit s'agir d'une commande séparée, car de nombreux shells courants ne l'aiment pas lorsque vous essayez d'écrire dans un descripteur de fichier à l'intérieur d'une substitution de commande, qui est ouverte dans la commande "external" qui utilise la substitution. moyen portable le plus simple de le faire.)Vous pouvez le regarder de manière moins technique et plus ludique, comme si les sorties des commandes se superposaient: command1 passe à command2, puis la sortie de printf saute par-dessus la commande 2 de sorte que commande2 ne l'attrape pas, puis la sortie de la commande 2 saute par-dessus la substitution de commande juste au moment où printf atterrit juste à temps pour être capturé par la substitution, de sorte qu'il se retrouve dans la variable, et la sortie de la commande2 continue à être joyeusement écrite sur la sortie standard, comme dans un tuyau normal.
De plus, si j'ai bien compris, il
$?
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 toutes transparentes pour le code de retour de la commande qu'elles contiennent. command2 devrait être propagé - ceci, et ne pas avoir à définir une fonction supplémentaire, est la raison pour laquelle je pense que cela pourrait être une solution légèrement meilleure que celle proposée par lesmana.Selon les mises en garde lesmana, il est possible que command1 finisse par utiliser les descripteurs de fichier 3 ou 4, aussi, pour être plus robuste, vous feriez:
Notez que j’utilise des commandes composées dans mon exemple, mais des sous-shell (utiliser
( )
au lieu de{ }
fonctionnera également, même s’il est peut-être moins efficace.)Les commandes héritent des descripteurs de fichier du processus qui les lance, de sorte que la deuxième ligne entière hérite du descripteur de fichier quatre et que la commande composée suivie de
3>&1
hérite du descripteur de fichier trois. Ainsi, le groupe4>&-
s'assure que la commande composée interne n'héritera pas du descripteur de fichier quatre et3>&-
n'héritera pas du descripteur de fichier trois. Ainsi, commande1 obtient un environnement plus «propre», plus standard. Vous pouvez également déplacer l’intérieur à4>&-
côté du3>&-
, mais j’imagine pourquoi ne pas simplement en limiter le plus possible la portée.Je ne suis pas sûr de la fréquence à laquelle les choses utilisent les descripteurs de fichier trois et quatre directement. 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 que le code écrit parfois directement dans le descripteur de fichier 3. devinez (je pourrais imaginer un programme vérifiant un descripteur de fichier pour voir s'il est ouvert, et l'utiliser si c'est le cas, ou se comporter différemment si ce n'est pas le cas). Donc, ce dernier est probablement préférable de garder à l'esprit et à utiliser pour les cas à usage général.
la source
-bash: 3: Bad file descriptor
.Si vous avez le paquet moreutils installé, vous pouvez utiliser l’ utilitaire faux - tuyau qui fait exactement ce que vous avez demandé.
la source
La solution de lesmana ci-dessus peut également être réalisée sans la surcharge de démarrer des sous-processus imbriqués en utilisant à la
{ .. }
place (en se rappelant que cette forme de commandes groupées doit toujours se terminer par des points-virgules). Quelque chose comme ça:J'ai vérifié cette construction avec la version de tableau de bord 0.5.5 et les versions 3.2.25 et 4.2.42 de Bash. Par conséquent, même si certains shells ne prennent pas en charge le
{ .. }
groupement, ils sont toujours compatibles POSIX.la source
set -o pipefail
in ksh ou un nombre quelconque dewait
commandes saupoudrées . Je pense que cela peut, au moins en partie, être un problème d’analyse syntaxique pour ksh, comme si je m’en tenais à l’utilisation de subshells, alors cela fonctionnerait bien, mais même avec unif
choix de la variante de sous-shell pour ksh mais en laissant les commandes composées pour d’autres, cela échoue .C'est portable, c'est-à-dire qu'il fonctionne avec n'importe quel shell compatible POSIX, ne nécessite pas que le répertoire actuel soit accessible en écriture et permet à plusieurs scripts utilisant la même astuce de s'exécuter simultanément.
Edit: voici une version plus forte suite aux commentaires de Gilles:
Edit2: et voici une variante légèrement plus légère suite au commentaire de dbiousjim:
la source
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Je suis d'accord que c'est plus facile avec Bash, mais dans certains contextes, savoir comment éviter Bash en vaut la peine.Ce qui suit est considéré comme un complément à la réponse de @Patrik, au cas où vous ne pourriez pas utiliser l’une des solutions courantes.
Cette réponse suppose que:
$PIPESTATUS
ni deset -o pipefail
Avant:,
foo | bar | baz
cependant, cela ne retourne que le code de sortie de la dernière commande (baz
)Wanted:
$?
ne doit pas être0
(true), si l'une des commandes du canal a échouéAprès:
A expliqué:
mktemp
. Cela crée généralement immédiatement un fichier dans/tmp
wait
est nécessaire pourksh
, carksh
sinon n'attend pas que toutes les commandes de canal soient terminées. Toutefois, veuillez noter qu'il existe des effets indésirables si certaines tâches d'arrière-plan sont présentes. Je l'ai donc commenté par défaut. Si l'attente ne fait pas mal, vous pouvez le commenter.read
retoursfalse
, donctrue
indique une erreurCeci peut être utilisé comme remplacement de plugin pour une seule commande et n'a besoin que de ce qui suit:
/proc/fd/N
Bogues:
Ce script a un bug dans le cas où l'
/tmp
espace est insuffisant. Si vous avez besoin de protection contre ce cas artificiel, aussi, vous pouvez le faire comme suit, mais cela présente l'inconvénient, que le nombre d'0
en000
fonction du nombre de commandes dans le tuyau, il est donc un peu plus compliqué:Notes de portabilité:
ksh
et des coquilles similaires qui n'attendent que la dernière commande de tuyau ont besoin du nonwait
commentéLe dernier exemple utilise
printf "%1s" "$?"
au lieu deecho -n "$?"
parce que c'est plus portable. Toutes les plateformes n'interprètent pas-n
correctement.printf "$?"
le ferait aussi bien, cependantprintf "%1s"
attrape certains cas au cas où vous exécutez le script sur une plate-forme vraiment cassée. (Lire: si vous programmezparanoia_mode=extreme
.)Les formats FD 8 et FD 9 peuvent être supérieurs sur les plates-formes prenant en charge plusieurs chiffres. AFAIR, un shell conforme à POSIX n'a besoin que de prendre en charge les chiffres uniques.
A été testé avec Debian 8.2
sh
,bash
,ksh
,ash
,sash
et mêmecsh
la source
Avec un peu de précaution, cela devrait fonctionner:
la source
Le bloc 'if' suivant ne sera exécuté que si 'commande' a réussi:
Plus précisément, vous pouvez exécuter quelque chose comme ceci:
Qui va exécuter
haconf -makerw
et stocker son stdout et stderr dans "$ haconf_out". Si la valeur renvoyéehaconf
est true, le bloc 'if' sera exécuté etgrep
indiquera "$ haconf_out", en essayant de le faire correspondre à "Le cluster est déjà accessible en écriture".Notez que les tuyaux se nettoient automatiquement; avec la redirection, vous devrez prendre soin de supprimer "$ haconf_out" lorsque vous aurez terminé.
Pas aussi élégant que
pipefail
, mais une alternative légitime si cette fonctionnalité n’est pas à la portée de la main.la source
la source
(Avec bash au moins) combiné avec
set -e
on peut utiliser un sous-shell pour émuler explicitement pipefail et sortir en cas d'erreurDonc si
foo
échoue pour une raison quelconque, le reste du programme ne sera pas exécuté et le script se terminera avec le code d'erreur correspondant. (Cela suppose que soitfoo
imprimée sa propre erreur, ce qui est suffisant pour comprendre la raison de l'échec)la source
EDIT : Cette réponse est fausse, mais intéressante, alors je la laisserai pour référence future.
L'ajout de a
!
à la commande inverse le code de retour.http://tldp.org/LDP/abs/html/exit-status.html
la source
ls
- pas inverser le code de sortie debogus_command