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
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 loptionpipefail
. - 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:
- 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.
- 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. - 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. - A pipe est créée et les commandes à gauche (
#part5
et#part6
) et à droite (filter >&4
) sont exécutés. La sortie defilter
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 defilter
est la sortie standard de la construction entière. - 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. -
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 defilter
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 ensomeprog 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 viabash 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/ff
–getopt
(ou similaire) analyse en utilisant leoptarg
, qui est lélément suivant dansARGV
, comme argument de-o
, donc il échoue. Si vous deviez créer un wrapper (par exemple,bash-pf
qui vient de faireexec /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. Utilisezmktemp
, 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 dewait
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 unif
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
niset -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 pourksh
, carksh
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
renvoiefalse
, donctrue
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 duwait
décommenté -
Le dernier exemple utilise
printf "%1s" "$?"
au lieu deecho -n "$?"
car cest plus portable. Toutes les plates-formes n’interprètent pas correctement-n
. -
printf "$?"
le ferait également, cependantprintf "%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 enparanoia_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êmecsh
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 debogus_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
pipestatus
dans zsh. Malheureusement, d’autres shells n’ont pas ‘ cette fonctionnalité.echo "$pipestatus[1]" "$pipestatus[2]"
.if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi