J'ai actuellement un script qui fait quelque chose comme
./a | ./b | ./c
Je veux le modifier pour que si l'un des a, b ou c se termine avec un code d'erreur, j'imprime un message d'erreur et j'arrête au lieu de transmettre une mauvaise sortie.
Quelle serait la manière la plus simple / la plus propre de le faire?
shell
error-handling
pipe
hugomg
la source
la source
&&|
qui signifierait "ne continuer le tube que si la commande précédente a réussi". Je suppose que vous pourriez aussi avoir|||
ce qui signifierait "continuer le tube si la commande précédente a échoué" (et éventuellement envoyer le message d'erreur comme celui de Bash 4|&
).a
,b
, lesc
commandes ne sont pas exécutées de façon séquentielle , mais en parallèle. En d' autres termes, les flux de données séquentiellement à partira
dec
, mais le réela
,b
etc
commandes de démarrage ( à peu près) dans le même temps.Réponses:
Si vous ne voulez vraiment pas que la deuxième commande se poursuive jusqu'à ce que la première soit réussie, vous devez probablement utiliser des fichiers temporaires. La version simple de cela est:
La redirection «1> & 2» peut également être abrégée «> & 2»; cependant, une ancienne version du shell MKS a mal géré la redirection d'erreur sans le «1» précédent, j'ai donc utilisé cette notation sans ambiguïté pour la fiabilité depuis des lustres.
Cela fuit des fichiers si vous interrompez quelque chose. La programmation shell à l'épreuve des bombes (plus ou moins) utilise:
La première ligne de déroutement dit `` exécuter les commandes '
rm -f $tmp.[12]; exit 1
' quand l'un des signaux 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE ou 15 SIGTERM se produit, ou 0 (lorsque le shell sort pour une raison quelconque). Si vous écrivez un script shell, le trap final n'a besoin que de supprimer le trap sur 0, qui est le trap de sortie du shell (vous pouvez laisser les autres signaux en place puisque le processus est sur le point de se terminer de toute façon).Dans le pipeline d'origine, il est possible que «c» lise les données de «b» avant que «a» ne soit terminé - c'est généralement souhaitable (cela donne du travail à plusieurs cœurs, par exemple). Si 'b' est une phase de 'tri', alors cela ne s'appliquera pas - 'b' doit voir toutes ses entrées avant de pouvoir générer l'une de ses sorties.
Si vous souhaitez détecter les commandes qui échouent, vous pouvez utiliser:
C'est simple et symétrique - il est trivial de s'étendre à un pipeline en 4 parties ou en N parties.
Une simple expérimentation avec 'set -e' n'a pas aidé.
la source
mktemp
outempfile
.trap
gardez à l'esprit que l'utilisateur peut toujours envoyerSIGKILL
à un processus pour le terminer immédiatement, auquel cas votre trap ne prendra pas effet. Il en va de même lorsque votre système subit une panne de courant. Lors de la création de fichiers temporaires, assurez-vous d'utilisermktemp
car cela placera vos fichiers quelque part, où ils seront nettoyés après un redémarrage (généralement dans/tmp
).Dans bash, vous pouvez utiliser
set -e
etset -o pipefail
au début de votre fichier. Une commande suivante./a | ./b | ./c
échouera lorsque l'un des trois scripts échoue. Le code retour sera le code retour du premier script ayant échoué.Notez que ce
pipefail
n'est pas disponible dans sh standard .la source
set
(pour 4 versions différentes au total), et voir comment cela affecte la sortie de la dernièreecho
.Vous pouvez également vérifier le
${PIPESTATUS[]}
tableau après l'exécution complète, par exemple si vous exécutez:Ensuite, il y
${PIPESTATUS}
aura un tableau de codes d'erreur de chaque commande dans le tube, donc si la commande du milieu échouait,echo ${PIPESTATUS[@]}
contiendrait quelque chose comme:et quelque chose comme ça après la commande:
vous permettra de vérifier que toutes les commandes du tube ont réussi.
la source
#!/bin/sh
, car si cesh
n'est pas bash, cela ne fonctionnera pas. (Facilement réparable en pensant à utiliser à la#!/bin/bash
place.)echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'
- renvoie 0 si$PIPESTATUS[@]
ne contient que 0 et des espaces (si toutes les commandes du tube réussissent).command1 && command2 | command3
si l'un de ces échecs échoue, votre solution renvoie une valeur non nulle.Malheureusement, la réponse de Johnathan nécessite des fichiers temporaires et les réponses de Michel et Imron nécessitent bash (même si cette question est étiquetée shell). Comme d'autres l'ont déjà souligné, il n'est pas possible d'interrompre le tube avant que les processus ultérieurs ne soient démarrés. Tous les processus sont lancés en même temps et seront donc tous exécutés avant que des erreurs ne puissent être communiquées. Mais le titre de la question posait également des questions sur les codes d'erreur. Ceux-ci peuvent être récupérés et examinés une fois le tuyau terminé pour déterminer si l'un des processus impliqués a échoué.
Voici une solution qui rattrape toutes les erreurs dans le tuyau et pas seulement les erreurs du dernier composant. C'est donc comme le pipefail de bash, juste plus puissant dans le sens où vous pouvez récupérer tous les codes d'erreur.
Pour détecter si quelque chose a échoué, une
echo
commande s'imprime sur une erreur standard en cas d'échec d'une commande. Ensuite, la sortie d'erreur standard combinée est enregistrée$res
et étudiée ultérieurement. C'est également pourquoi l'erreur standard de tous les processus est redirigée vers la sortie standard. Vous pouvez également envoyer cette sortie/dev/null
ou la laisser comme un autre indicateur que quelque chose s'est mal passé. Vous pouvez remplacer la dernière redirection vers/dev/null
par un fichier si vous ne souhaitez pas stocker la sortie de la dernière commande n'importe où.Pour jouer plus avec cette construction et pour vous convaincre que cela fait vraiment ce qu'il faut, j'ai remplacé
./a
,./b
et./c
par des sous-shell qui exécutentecho
,cat
etexit
. Vous pouvez l'utiliser pour vérifier que cette construction transfère réellement toute la sortie d'un processus à un autre et que les codes d'erreur sont correctement enregistrés.la source