La plupart des guides Linux se composent de pages telles que "vous devez exécuter command_1
, puis command_2
, puis command_3
"etc. Comme je ne veux pas perdre mon temps à les exécuter tous manuellement, je préfère créer un script
command_1
command_2
command_3
et l'exécuter une fois. Mais, le plus souvent, certaines commandes échouent et je n'ai aucune idée de celles qui ont échoué. De plus, généralement toutes les commandes restantes n'ont aucun sens si quelque chose échoue plus tôt. Donc un meilleur script serait quelque chose comme
(command_1 && echo OK command_1 || (echo FAILED command_1; false) )
&& (command_2 && echo OK command_2 || (echo FAILED command_2; false) )
&& (command_3 && echo OK command_3 || (echo FAILED command_3; false) )
&& echo DONE
|| echo FAILED
Mais cela nécessite d'écrire trop de code passe-partout, de répéter chaque commande 3 fois, et il y a trop de chances que je frappe mal certains accolades. Existe-t-il un moyen plus pratique de faire ce que fait le dernier script? En particulier:
Il existe 2 types de solutions:
try
et en divisant les commandes chaînées en différentes ceux (réponse de Jasen).Vous êtes rock, mais je vais laisser cette question ouverte pendant un moment. Peut-être que quelqu'un connaît une solution qui satisfait les deux besoins (imprimer la commande échouée sur la dernière ligne et autoriser le copier-coller des commandes sans leurs modifications).
Une option serait de placer les commandes dans un script bash et de le démarrer avec set -e
.
Cela entraînera la fin prématurée du script si une commande se termine avec un état de sortie différent de zéro.
Voir aussi cette question sur le débordement de pile: https://stackoverflow.com/q/19622198/82819
Pour imprimer l'erreur, vous pouvez utiliser
trap 'do_something' ERR
Où do_something
est une commande que vous créeriez pour afficher l'erreur.
Voici un exemple de script pour voir comment cela fonctionne:
#!/bin/bash
set -e
trap 'echo "******* FAILED *******" 1>&2' ERR
echo 'Command that succeeds' # this command works
ls non_existent_file # this should fail
echo 'Unreachable command' # and this is never called
# due to set -e
Et voici la sortie:
$ ./test.sh
Command that succeeds
ls: cannot access 'non_existent_file': No such file or directory
******* FAILED *******
De plus, comme mentionné par @ jick , gardez à l'esprit que l'état de sortie d'un pipeline est par défaut l'état de sortie du final commande dedans. Cela signifie que si une commande non finale du pipeline échoue, elle ne sera pas interceptée par set -e
. Pour résoudre ce problème si vous le souhaitez, vous pouvez utiliser set -o pipefail
Comme suggéré mon @ glenn jackman et @ Monty Harder , en utilisant une fonction car le gestionnaire peut rendre le script plus lisible, car il évite les guillemets imbriqués. Puisque nous utilisons une fonction de toute façon maintenant, j'ai supprimé set -e
entièrement, et utilisé exit 1
dans le gestionnaire, ce qui pourrait également le rendre plus lisible pour certains:
#!/bin/bash
error_handler() {
echo "******* FAILED *******" 1>&2
exit 1
}
trap error_handler ERR
echo 'Command that succeeds' # this command works
ls non_existent_file # this should fail
echo 'Unreachable command' # and this is never called
# due to the exit in the handler
La sortie est identique à celle ci-dessus, bien que l'état de sortie du script soit différent.
Vous voulez une solution inhabituelle? Si vous avez make
installé, vous pouvez placer la liste des commandes dans un Makefile
pour que make
s'exécute. Avantage supplémentaire: vous n'avez pas à vérifier si des erreurs se sont produites. make
vérifiera automatiquement la valeur de retour de chaque commande. S'il est différent de zéro, la recette se termine avec une erreur. Si vous souhaitez ignorer les erreurs avec certaines commandes, chaînez-les avec || true
.
Exemple de Makefile:
.PHONY: all
.SILENT:
all:
echo "Started list of commands."
true
echo "Executing a command which will fail, but I want to ignore failure."
false || true
echo "Executing a command which will definitely fail."
false
echo "This code will not be reached."
Remarque: assurez-vous (heh) de mettre en retrait vos commandes comme ci-dessus, mais avec un onglet.
Je crois que cela ne fonctionne que pour bash, mais vous pouvez essayer:
set -o xtrace
set -o errexit
Ou, si vous voulez être concis,
set -ex
Cela fera deux choses: errexit
(-e) abandonnera le script en cas d'erreur, et xtrace
(-x) affichera chaque commande juste avant que bash l'exécute, donc au cas où d'échec, vous savez exactement ce qu'il exécutait.
Un inconvénient est qu'il encombrera la sortie, mais tant que vous êtes d'accord, c'est une assez bonne solution avec un travail minimal.
set -o pipefail
: sinon, si vous exécutez foo | bar
, l'échec de foo
sera ignoré en silence. (Attention: il modifie subtilement le "code de sortie" d'une instruction pipe, donc utilisez-le avec prudence si vous modifiez le script de quelqu'un d'autre. De plus, parfois vous ne vous souciez pas de l'échec de foo
, donc évidemment vous ne peut pas utiliser pipefail
dans un tel cas.)Vous pouvez faire quelque chose comme ça:
$ for f in command_1 command_2 command_3 command_4
do
$f
rc=$?
if [[ 0 != $rc ]]; then echo Failed command: ${f}; break; fi
done
Cela suppose que les commandes n'ont pas d'options, si c'est le cas, vous devrez placer chaque jeu de commandes/options entre guillemets.
failed=0
try(){
(( failed )) && return
"$@"
ec=$?
if (( ec ))
then
failed=$ec
echo "failed($failed): $*" >&2
fi
}
try something args
try other-thing more args
if (( failed ))
then
echo "something went wrong: see above." >&2
exit 1
fi
Utilisez cette solution si vous souhaitez le faire dans le shell interactif:
set -x; command_1 && command_2 && command_3 && echo "Everything OK" || echo "Error while executing last command"; set +x
Sortie lorsque toutes les commandes réussissent:
+ command_1
output of command 1
+ command_2
output of command 2
+ command_3
output of command 3
+ echo "Everything OK"
Everything OK
+ set +x
Sortie lorsque le command_2
échoue:
+ command_1
output of command 1
+ command_2
output of command 2
+ echo "Error while executing last command"
+ set +x
Le set -x
commande active la journalisation de toutes les commandes exécutées. Les commandes enregistrées sont préfixées par un ou plusieurs avantages (le style peut être modifié à l'aide de la variable $PS4
). set +x
désactive ce mode.
Vous devez mettre le set -x
et set +x
à la même ligne car set -x
active la journalisation des commandes exécutées toutes - y compris $COMMAND_Prompt
et les commandes qu'il exécute.
Vous pouvez également l'utiliser pour les scripts Shell:
#!/bin/bash -ex
command_1
command_2
command_3
Les options de shell peuvent être intégrées dans la ligne Shebang. -e
fait quitter Shell après la première commande ayant échoué (comme lorsque toutes les lignes se terminent par &&
). -x
active la journalisation des commandes. Notez que vous ne pouvez passer que l'argument one à l'interpréteur. Utilisation /usr/bin/env
pour passer plusieurs arguments (#!/usr/bin/env -S bash -ex arg1 arg2 argN
).
Peut-être juste une fonction pour ce que vous faites déjà:
function run_cmd
( $* && echo OK $* || ( echo FAILED $*; false ) )
run_cmd echo 1 &&
run_cmd false 2 &&
run_cmd echo 3 &&
echo DONE || echo FAILED
La sortie de ceci est:
1
OK echo 1
FAILED false 2
FAILED