Quitter le script shell en fonction du code de sortie du processus
378
J'ai un script shell qui exécute un certain nombre de commandes. Comment faire pour quitter le script shell si l'une des commandes se termine avec un code de sortie différent de zéro?
J'ai répondu en supposant que vous utilisez bash, mais si c'est un shell très différent, pouvez-vous spécifier dans votre message?
Martin W
Méthode difficile: testez la valeur de $?après chaque commande. Méthode simple: mettez set -eou #!/bin/bash -een haut de votre script Bash.
mwfearnley
Réponses:
495
Après chaque commande, le code de sortie peut être trouvé dans la $?variable afin que vous ayez quelque chose comme:
ls -al file.ext
rc=$?;if[[ $rc !=0]];then exit $rc;fi
Vous devez faire attention aux commandes pipées car la $?seule vous donne le code retour du dernier élément du tube donc, dans le code:
ls -al file.ext | sed 's/^/xx: /"
ne renverra pas de code d'erreur si le fichier n'existe pas (puisque la sedpartie du pipeline fonctionne réellement, renvoyant 0).
Le bashshell fournit en fait un tableau qui peut aider dans ce cas, c'est-à-dire PIPESTATUS. Ce tableau a un élément pour chacun des composants du pipeline, auquel vous pouvez accéder individuellement comme ${PIPESTATUS[0]}:
pax> false | true ; echo ${PIPESTATUS[0]}1
Notez que cela vous donne le résultat de la falsecommande, pas le pipeline entier. Vous pouvez également obtenir la liste complète à traiter comme bon vous semble:
Même fonctionnalité dans une seule ligne de code portable: ls -al file.ext || exit $?([[]] n'est pas portable)
MarcH
19
MarcH, je pense que vous trouverez que [[ ]]c'est assez portable bash, c'est ce que la question est étiquetée :-) Curieusement, lsça ne marche pas command.comdonc ce n'est pas portable non plus, spécieux je sais, mais c'est le même genre d'argument que vous présent.
paxdiablo
39
Je sais que c'est ancien, mais il convient de noter que vous pouvez obtenir le code de sortie des commandes dans un tube via le tableau PIPESTATUS(c'est-à-dire ${PIPESTATUS[0]}pour la première commande, ${PIPESTATUS[1]}pour la seconde ou ${PIPESTATUS[*]}pour une liste de toutes les stati de sortie.
DevSolar
11
Il convient de souligner que les scripts shell élégants et idiomatiques doivent très rarement être examinés $?directement. Vous voulez généralement quelque chose comme if ls -al file.ext; then : nothing; else exit $?; fiqui, bien sûr, comme @MarcH dit est équivalent, ls -al file.ext || exit $?mais si les clauses thenou elsesont un peu plus complexes, elles sont plus faciles à maintenir.
tripleee
9
[[ $rc != 0 ]]vous donnera une 0: not foundou une 1: not founderreur. Cela devrait être changé en [ $rc -ne 0 ]. rc=$?Peut également être retiré et utilisé [ $? -ne 0 ].
CurtisLeeBolin
223
Si vous voulez travailler avec $ ?, vous devrez le vérifier après chaque commande, car $? est mis à jour après la fin de chaque commande. Cela signifie que si vous exécutez un pipeline, vous n'obtiendrez que le code de sortie du dernier processus du pipeline.
Une autre approche consiste à le faire:
set-e
set-o pipefail
Si vous placez cela en haut du script shell, il semblerait que bash s'en occupe pour vous. Comme l'a noté une affiche précédente, "set -e" provoquera la sortie de bash avec une erreur sur toute commande simple. "set -o pipefail" provoquera la sortie de bash avec une erreur sur n'importe quelle commande dans un pipeline.
Voir ici ou ici pour un peu plus de discussion sur ce problème. Voici la section du manuel bash sur l'ensemble intégré.
Cela devrait vraiment être la meilleure réponse: c'est beaucoup, beaucoup plus facile à faire que d'utiliser PIPESTATUSet de vérifier les codes de sortie partout.
candu
2
#!/bin/bash -eest le seul moyen de démarrer un script shell. Vous pouvez toujours utiliser des choses comme foo || handle_error $?si vous devez réellement examiner les états de sortie.
Davis Herring
53
" set -e" est probablement le moyen le plus simple de procéder. Mettez juste cela avant toute commande dans votre programme.
@SwaroopCH set -evotre script sera abandonné si une commande de votre script se termine avec un état d'erreur et que vous n'avez pas géré cette erreur.
Andrew
2
set -eest 100% équivalent à set -o errexitce qui, contrairement au premier, peut être recherché. Recherchez opengroup + errexit pour la documentation officielle.
MarcH
30
Si vous appelez simplement exit dans le bash sans paramètre, il retournera le code de sortie de la dernière commande. Combiné avec OR, le bash ne devrait appeler exit que si la commande précédente échoue. Mais je n'ai pas testé cela.
command1 || sortie;
command2 || sortie;
Le Bash stockera également le code de sortie de la dernière commande dans la variable $ ?.
Comment obtenir le code de sortie de cmd1incmd1|cmd2
Tout d'abord, notez que le cmd1code de sortie peut être différent de zéro et ne signifie toujours pas une erreur. Cela se produit par exemple dans
cmd | head -1
vous pourriez observer un état de sortie 141 (ou 269 avec ksh93) de cmd1, mais c'est parce qu'il a cmdété interrompu par un signal SIGPIPE lorsqu'il s'est
head -1terminé après avoir lu une ligne.
Connaître l'état de sortie des éléments d'un pipeline
cmd1 | cmd2 | cmd3
une. avec zsh:
Les codes de sortie sont fournis dans le tableau spécial pipestatus.
cmd1le code de sortie est dans $pipestatus[1], le cmd3code de sortie est
$pipestatus[3], donc c'est $?toujours la même chose que
$pipestatus[-1].
b. avec bash:
Les codes de sortie sont fournis dans le PIPESTATUStableau spécial.
cmd1le code de sortie est dans ${PIPESTATUS[0]}, le cmd3code de sortie est
${PIPESTATUS[2]}, donc c'est $?toujours la même chose que
${PIPESTATUS: -1}.
# this will trap any errors or commands with non-zero exit status# by calling function catch_errors()
trap catch_errors ERR;## ... the rest of the script goes here# function catch_errors(){# do whatever on errors# #
echo "script aborted, because of errors";
exit 0;}
+1 C'était la solution la plus simple que je cherchais. De plus, vous pouvez également écrire if (! command)si vous attendez un code d'erreur différent de zéro de la commande.
Berci
c'est pour les commandes séquentielles .. que faire si je veux lancer ces 3 en parallèle et tuer tout le monde si l'un d'eux échoue?
Vasile Surdu
4
##------------------------------------------------------------------------------# run a command on failure exit with message# doPrintHelp: doRunCmdOrExit "$cmd"# call by:# set -e ; doRunCmdOrExit "$cmd" ; set +e#------------------------------------------------------------------------------
doRunCmdOrExit(){
cmd="$@";
doLog "DEBUG running cmd or exit: \"$cmd\""
msg=$($cmd 2>&1)
export exit_code=$?# if occured during the execution exit with error
error_msg="Failed to run the command:
\"$cmd\" with the output:
\"$msg\" !!!"if[ $exit_code -ne 0];then
doLog "ERROR $msg"
doLog "FATAL $msg"
doExit "$exit_code""$error_msg"else#if no errors occured just log the message
doLog "DEBUG : cmdoutput : \"$msg\""
doLog "INFO $msg"fi}#eof func doRunCmdOrExit
$?
après chaque commande. Méthode simple: mettezset -e
ou#!/bin/bash -e
en haut de votre script Bash.Réponses:
Après chaque commande, le code de sortie peut être trouvé dans la
$?
variable afin que vous ayez quelque chose comme:Vous devez faire attention aux commandes pipées car la
$?
seule vous donne le code retour du dernier élément du tube donc, dans le code:ne renverra pas de code d'erreur si le fichier n'existe pas (puisque la
sed
partie du pipeline fonctionne réellement, renvoyant 0).Le
bash
shell fournit en fait un tableau qui peut aider dans ce cas, c'est-à-direPIPESTATUS
. Ce tableau a un élément pour chacun des composants du pipeline, auquel vous pouvez accéder individuellement comme${PIPESTATUS[0]}
:Notez que cela vous donne le résultat de la
false
commande, pas le pipeline entier. Vous pouvez également obtenir la liste complète à traiter comme bon vous semble:Si vous vouliez obtenir le plus gros code d'erreur d'un pipeline, vous pouvez utiliser quelque chose comme:
Cela passe par chacun des
PIPESTATUS
éléments tour à tour, en le stockantrc
s'il était supérieur à larc
valeur précédente .la source
ls -al file.ext || exit $?
([[]] n'est pas portable)[[ ]]
c'est assez portablebash
, c'est ce que la question est étiquetée :-) Curieusement,ls
ça ne marche pascommand.com
donc ce n'est pas portable non plus, spécieux je sais, mais c'est le même genre d'argument que vous présent.PIPESTATUS
(c'est-à-dire${PIPESTATUS[0]}
pour la première commande,${PIPESTATUS[1]}
pour la seconde ou${PIPESTATUS[*]}
pour une liste de toutes les stati de sortie.$?
directement. Vous voulez généralement quelque chose commeif ls -al file.ext; then : nothing; else exit $?; fi
qui, bien sûr, comme @MarcH dit est équivalent,ls -al file.ext || exit $?
mais si les clausesthen
ouelse
sont un peu plus complexes, elles sont plus faciles à maintenir.[[ $rc != 0 ]]
vous donnera une0: not found
ou une1: not found
erreur. Cela devrait être changé en[ $rc -ne 0 ]
.rc=$?
Peut également être retiré et utilisé[ $? -ne 0 ]
.Si vous voulez travailler avec $ ?, vous devrez le vérifier après chaque commande, car $? est mis à jour après la fin de chaque commande. Cela signifie que si vous exécutez un pipeline, vous n'obtiendrez que le code de sortie du dernier processus du pipeline.
Une autre approche consiste à le faire:
Si vous placez cela en haut du script shell, il semblerait que bash s'en occupe pour vous. Comme l'a noté une affiche précédente, "set -e" provoquera la sortie de bash avec une erreur sur toute commande simple. "set -o pipefail" provoquera la sortie de bash avec une erreur sur n'importe quelle commande dans un pipeline.
Voir ici ou ici pour un peu plus de discussion sur ce problème. Voici la section du manuel bash sur l'ensemble intégré.
la source
PIPESTATUS
et de vérifier les codes de sortie partout.#!/bin/bash -e
est le seul moyen de démarrer un script shell. Vous pouvez toujours utiliser des choses commefoo || handle_error $?
si vous devez réellement examiner les états de sortie."
set -e
" est probablement le moyen le plus simple de procéder. Mettez juste cela avant toute commande dans votre programme.la source
set -e
votre script sera abandonné si une commande de votre script se termine avec un état d'erreur et que vous n'avez pas géré cette erreur.set -e
est 100% équivalent àset -o errexit
ce qui, contrairement au premier, peut être recherché. Recherchez opengroup + errexit pour la documentation officielle.Si vous appelez simplement exit dans le bash sans paramètre, il retournera le code de sortie de la dernière commande. Combiné avec OR, le bash ne devrait appeler exit que si la commande précédente échoue. Mais je n'ai pas testé cela.
Le Bash stockera également le code de sortie de la dernière commande dans la variable $ ?.
la source
la source
exit $?
(sans parens)?http://cfaj.freeshell.org/shell/cus-faq-2.html#11
Comment obtenir le code de sortie de
cmd1
incmd1|cmd2
Tout d'abord, notez que le
cmd1
code de sortie peut être différent de zéro et ne signifie toujours pas une erreur. Cela se produit par exemple dansvous pourriez observer un état de sortie 141 (ou 269 avec ksh93) de
cmd1
, mais c'est parce qu'il acmd
été interrompu par un signal SIGPIPE lorsqu'il s'esthead -1
terminé après avoir lu une ligne.Connaître l'état de sortie des éléments d'un pipeline
cmd1 | cmd2 | cmd3
une. avec zsh:
Les codes de sortie sont fournis dans le tableau spécial pipestatus.
cmd1
le code de sortie est dans$pipestatus[1]
, lecmd3
code de sortie est$pipestatus[3]
, donc c'est$?
toujours la même chose que$pipestatus[-1]
.b. avec bash:
Les codes de sortie sont fournis dans le
PIPESTATUS
tableau spécial.cmd1
le code de sortie est dans${PIPESTATUS[0]}
, lecmd3
code de sortie est${PIPESTATUS[2]}
, donc c'est$?
toujours la même chose que${PIPESTATUS: -1}
....
Pour plus de détails, voir le lien suivant .
la source
pour bash:
la source
En bash, c'est facile, il suffit de les lier avec &&:
Vous pouvez également utiliser la construction imbriquée if:
la source
if (! command)
si vous attendez un code d'erreur différent de zéro de la commande.la source
$*
; utilisez"$@"
plutôt pour conserver les espaces et les caractères génériques.