Je veux exécuter une commande longue dans Bash, capturer son état de sortie et envoyer sa sortie.
Alors je fais ça:
command | tee out.txt
ST=$?
Le problème est que la variable ST capture l'état de sortie de tee
et non de commande. Comment puis-je resoudre ceci?
Notez que la commande est longue et rediriger la sortie vers un fichier pour la visualiser plus tard n'est pas une bonne solution pour moi.
bash
shell
error-handling
pipe
fil volant
la source
la source
Réponses:
Il existe une variable Bash interne appelée
$PIPESTATUS
; c'est un tableau qui contient l'état de sortie de chaque commande dans votre dernier pipeline de commandes de premier plan.Ou une autre alternative qui fonctionne également avec d'autres shells (comme zsh) serait d'activer pipefail:
La première option ne fonctionne pas en
zsh
raison d'une syntaxe légèrement différente.la source
exit ${PIPESTATUS[0]}
.utiliser bash
set -o pipefail
est utilela source
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
et ensuite faire la commande, et immédiatement aprèsset +o pipefail
pour désactiver l'option.-o pipefail
il saurait si le tuyau échoue, mais si à la fois «commande» et «tee» échouent, il recevra le code de sortie de «tee».Solution stupide: les connecter via un canal nommé (mkfifo). Ensuite, la commande peut être exécutée en second.
la source
mkfifo
et peuvent à la place nécessitermknod -p
si je me souviens bien.mkfifo
oumknod -p
: dans mon cas, la commande appropriée pour créer le fichier pipe étaitmknod FILE_NAME p
.Il y a un tableau qui vous donne le statut de sortie de chaque commande dans un tube.
la source
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 l'état de sortie
someprog
et la sortie defilter
.Voici ma solution:
Voir ma réponse pour la même question sur unix.stackexchange.com pour une explication détaillée et une alternative sans sous-coquilles et quelques mises en garde.
la source
En combinant
PIPESTATUS[0]
et le résultat de l'exécution de laexit
commande dans un sous-shell, vous pouvez accéder directement à la valeur de retour de votre commande initiale:command | tee ; ( exit ${PIPESTATUS[0]} )
Voici un exemple:
te donnera:
return value: 1
la source
VALUE=$(might_fail | piping)
qui ne définira pas PIPESTATUS dans le shell principal mais définira son niveau d'erreur. En utilisant:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
je reçois ce que je voulais.command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
au cas où pas le tee mais le filtrage grepJ'ai donc voulu apporter une réponse comme celle de lesmana, mais je pense que la mienne est peut-être une solution pure-Bourne-shell un peu plus simple et légèrement plus avantageuse:
Je pense que cela s'explique mieux de l'intérieur - command1 exécutera et imprimera sa sortie régulière sur stdout (descripteur de fichier 1), puis une fois cela fait, printf exécutera et imprimera le code de sortie d'icommand1 sur sa stdout, mais cette stdout est redirigée vers descripteur de fichier 3.
Pendant que command1 est en cours d'exécution, sa sortie standard est dirigée vers command2 (la sortie de printf ne parvient jamais à command2 car nous l'envoyons vers le descripteur de fichier 3 au lieu de 1, qui est ce que le tuyau lit). Ensuite, nous redirige la sortie de command2 vers le descripteur de fichier 4, afin qu'il reste également en dehors du descripteur de fichier 1 - parce que nous voulons que le descripteur de fichier 1 soit gratuit un peu plus tard, car nous ramènerons la sortie printf du descripteur de fichier 3 dans le descripteur de fichier 1 - parce que c'est ce que la substitution de commandes (les backticks) capturera et c'est ce qui sera placé dans la variable.
Le dernier morceau de magie est que
exec 4>&1
nous l'avons d' abord fait comme une commande distincte - il ouvre le descripteur de fichier 4 comme une copie de la sortie standard du shell externe. La substitution de commandes capturera tout ce qui est écrit sur la norme du point de vue des commandes à l'intérieur - mais puisque la sortie de command2 va dans le descripteur de fichier 4 en ce qui concerne la substitution de commandes, la substitution de commandes ne la capture pas - cependant une fois obtient "hors" de la substitution de commande, il va effectivement toujours au descripteur de fichier global du script 1.(La
exec 4>&1
doit être une commande distincte 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 ouvert dans la commande "externe" qui utilise la substitution. moyen portable le plus simple de le faire.)Vous pouvez le regarder d'une manière moins technique et plus ludique, comme si les sorties des commandes se dépassaient les unes les autres: command1 redirige vers command2, puis la sortie du printf saute par-dessus la commande 2 afin que la commande2 ne l'attrape pas, puis la sortie de la commande 2 saute au-dessus et en dehors de la substitution de commande juste au moment où printf atterrit juste à temps pour être capturé par la substitution afin qu'il finisse dans la variable, et la sortie de command2 continue à être joyeusement écrite sur la sortie standard, tout comme dans un tuyau normal.
Aussi, si je comprends bien,
$?
contiendra toujours le code retour de la deuxième commande dans le canal, car les affectations de variables, les substitutions de commandes et les commandes composées sont toutes effectivement transparentes pour le code retour de la commande à l'intérieur, donc l'état de retour de command2 devrait se propager - ceci, et ne pas avoir à définir de fonction supplémentaire, c'est pourquoi je pense que cela pourrait être une meilleure solution que celle proposée par lesmana.Selon les mises en garde que mentionne lesmana, il est possible que command1 finisse par utiliser des descripteurs de fichiers 3 ou 4, donc pour être plus robuste, vous feriez:
Notez que j'utilise des commandes composées dans mon exemple, mais les sous-coquilles (l'utilisation à la
( )
place de{ }
fonctionnera également, bien que peut-être moins efficace.)Les commandes héritent des descripteurs de fichiers du processus qui les lance, de sorte que la deuxième ligne entière héritera du descripteur de fichiers quatre, et la commande composée suivie de
3>&1
héritera du descripteur de fichiers trois. Ainsi, la commande4>&-
s'assure que la commande interne composée n'héritera pas du descripteur de fichier quatre et3>&-
qu'elle n'héritera pas du descripteur de fichier trois, de sorte que command1 obtient un environnement plus propre et plus standard. Vous pouvez également déplacer l'intérieur à4>&-
côté du3>&-
, mais je pense pourquoi ne pas limiter autant que possible sa portée.Je ne sais pas combien de fois les choses utilisent directement les descripteurs de fichiers trois et quatre - je pense que la plupart du temps, les programmes utilisent des appels système qui renvoient des descripteurs de fichiers non utilisés pour le moment, mais parfois le code écrit directement dans le descripteur de fichier 3, je guess (je pourrais imaginer un programme vérifiant un descripteur de fichier pour voir s'il est ouvert, et l'utilisant s'il l'est, ou se comportant différemment en conséquence s'il ne l'est pas). Il est donc préférable de garder ce dernier à l'esprit et de l'utiliser pour les cas à usage général.
la source
Dans Ubuntu et Debian, vous pouvez
apt-get install moreutils
. Il contient un utilitaire appelémispipe
qui renvoie l'état de sortie de la première commande du canal.la source
Contrairement à la réponse de @ cODAR, cela renvoie le code de sortie d'origine de la première commande et pas seulement 0 pour le succès et 127 pour l'échec. Mais comme @Chaoran l'a souligné, vous pouvez simplement appeler
${PIPESTATUS[0]}
. Il est cependant important que tout soit mis entre parenthèses.la source
En dehors de bash, vous pouvez faire:
C'est utile par exemple dans les scripts ninja où le shell devrait se trouver
/bin/sh
.la source
PIPESTATUS [@] doit être copié dans un tableau immédiatement après le retour de la commande pipe. Toute lecture de PIPESTATUS [@] effacera le contenu. Copiez-le dans un autre tableau si vous prévoyez de vérifier l'état de toutes les commandes de canalisation. "$?" est la même valeur que le dernier élément de "$ {PIPESTATUS [@]}", et sa lecture semble détruire "$ {PIPESTATUS [@]}", mais je ne l'ai pas absolument vérifié.
Cela ne fonctionnera pas si le tuyau est dans un sous-shell. Pour une solution à ce problème,
voir bash pipestatus dans la commande backticked?
la source
La façon la plus simple de le faire en simple bash est d'utiliser la substitution de processus au lieu d'un pipeline. Il existe plusieurs différences, mais elles n'ont probablement pas beaucoup d'importance pour votre cas d'utilisation:
pipefail
option et laPIPESTATUS
variable ne sont pas pertinentes pour traiter la substitution.Avec la substitution de processus, bash démarre simplement le processus et l'oublie, il n'est même pas visible dans
jobs
.Mises à part les différences,
consumer < <(producer)
etproducer | consumer
sont essentiellement équivalentes.Si vous voulez inverser lequel est le processus "principal", il vous suffit de retourner les commandes et la direction de la substitution vers
producer > >(consumer)
. Dans ton cas:Exemple:
Comme je l'ai dit, il y a des différences par rapport à l'expression pipe. Le processus peut ne jamais s'arrêter, sauf s'il est sensible à la fermeture du tuyau. En particulier, il peut continuer d'écrire des choses sur votre sortie standard, ce qui peut prêter à confusion.
la source
Solution de coque pure:
Et maintenant avec le second
cat
remplacé parfalse
:Veuillez noter que le premier chat échoue également, car sa sortie est fermée. L'ordre des commandes ayant échoué dans le journal est correct dans cet exemple, mais ne vous y fiez pas.
Cette méthode permet de capturer stdout et stderr pour les commandes individuelles afin que vous puissiez ensuite les vider également dans un fichier journal si une erreur se produit, ou simplement les supprimer si aucune erreur (comme la sortie de dd).
la source
Base sur la réponse de @ brian-s-wilson; cette fonction d'assistance bash:
utilisé ainsi:
1: get_bad_things doit réussir, mais il ne doit produire aucune sortie; mais nous voulons voir la sortie qu'elle produit
2: tout pipeline doit réussir
la source
Il peut parfois être plus simple et plus clair d'utiliser une commande externe, plutôt que de fouiller dans les détails de bash. pipeline , à partir du langage de script de processus minimal execline , se termine avec le code retour de la deuxième commande *, tout comme le fait un
sh
pipeline, mais contrairement àsh
cela, il permet d'inverser la direction du tuyau, afin que nous puissions capturer le code retour du producteur processus (ce qui suit est tout sur lash
ligne de commande, mais avecexecline
installé):L'utilisation
pipeline
présente les mêmes différences avec les pipelines bash natifs que la substitution de processus bash utilisée dans la réponse # 43972501 .* En fait,
pipeline
ne se ferme pas du tout sauf en cas d'erreur. Il s'exécute dans la deuxième commande, c'est donc la deuxième commande qui fait le retour.la source