web-dev-qa-db-fra.com

Raise error dans un script Bash

Je souhaite générer une erreur dans un script Bash avec le message "Echecs des tests !!!". Comment faire cela en Bash?

Par exemple:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
59
user3078630

Cela dépend de l'endroit où vous voulez que le message d'erreur soit enregistré. 

Vous pouvez faire ce qui suit:

echo "Error!" > logfile.log
exit 125

Ou le suivant:

echo "Error!" 1>&2
exit 64

Lorsque vous déclenchez une exception, vous arrêtez l'exécution du programme. 

Vous pouvez également utiliser quelque chose comme exit xxxxxx est le code d'erreur que vous pouvez souhaiter retourner au système d'exploitation (de 0 à 255). Ici, 125 et 64 ne sont que des codes aléatoires avec lesquels vous pouvez sortir. Lorsque vous devez indiquer au système d'exploitation que le programme s'est arrêté anormalement (par exemple, une erreur s'est produite), vous devez transmettre un code de sortie non nul à exit

Comme @chepner a souligné , vous pouvez utiliser exit 1, ce qui signifie que une erreur non spécifiée

77
ForceBru

Traitement d'erreur de base

Si votre coureur de cas de test retourne un code non nul pour les tests ayant échoué, vous pouvez simplement écrire:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

Ou même plus court:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

Ou le plus court:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Pour quitter avec le code de sortie de test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Gestion avancée des erreurs

Si vous souhaitez adopter une approche plus globale, vous pouvez avoir un gestionnaire d’erreur:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

puis appelez-le après avoir exécuté votre scénario de test:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

ou 

run_test_case test_case_x || exit_if_error $? "Test case x failed"

Les avantages d'avoir un gestionnaire d'erreurs comme exit_if_error sont les suivants:

  • nous pouvons normaliser toute la logique de traitement des erreurs telle que journalisation , l'impression d'un suivi de pile , notification, nettoyage, etc., en un seul endroit
  • en faisant en sorte que le gestionnaire d'erreur obtienne le code d'erreur en argument, nous pouvons éviter l'appelant du fouillis de blocs if qui testent les erreurs des codes de sortie
  • si nous avons un gestionnaire de signal (en utilisant trap ), nous pouvons invoquer le gestionnaire d’erreur à partir de là.

Articles Similaires

16
codeforester

Vous pouvez aborder ce problème de plusieurs manières. En supposant que l’une de vos exigences est d’exécuter un script/une fonction shell contenant quelques commandes shell et de vérifier si le script s’est exécuté correctement et de générer des erreurs en cas d’échec. 

Les commandes du shell en général reposent généralement sur les codes de sortie renvoyés pour permettre au shell de savoir s’il a réussi ou échoué en raison de certains événements inattendus. 

Donc, ce que vous voulez faire tombe sur ces deux catégories

  • sortir en cas d'erreur
  • sortie et nettoyage en cas d'erreur

En fonction de celle que vous voulez faire, il existe des options de shell à utiliser. Dans le premier cas, le shell fournit une option avec set -e et pour le second vous pouvez faire un trap sur EXIT

Devrais-je utiliser exit dans mon script/ma fonction?

L’utilisation de exit améliore généralement la lisibilité Dans certaines routines, une fois que vous connaissez la réponse, vous souhaitez quitter la routine d’appel immédiatement. Si la routine est définie de telle sorte qu’elle ne nécessite aucun nettoyage supplémentaire une fois qu’elle détecte une erreur, ne pas la quitter immédiatement signifie que vous devez écrire davantage de code.

Ainsi, si vous devez effectuer des actions de nettoyage sur le script pour en nettoyer l’arrêt, il est préférable d’utiliser not d’utiliser exit

Devrais-je utiliser set -e pour une erreur en sortie?

Non!

set -e était une tentative pour ajouter "la détection automatique d'erreur" au shell. Son objectif était de faire avorter le Shell chaque fois qu'une erreur se produisait, mais il comporte de nombreux pièges potentiels, par exemple: 

  • Les commandes faisant partie d'un si test sont immunisées. Dans l'exemple, si vous vous attendez à ce que la vérification test apparaisse sur le répertoire non existant, ce ne serait pas le cas, cela passerait à la condition else

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
    
  • Les commandes dans un pipeline autre que le dernier sont immunisées. Dans l'exemple ci-dessous, le code de sortie de la commande exécutée en dernier lieu (la plus à droite) est considéré (cat) et a abouti. Cela pourrait être évité en définissant l'option set -o pipefail mais cela reste une mise en garde. 

    set -e
    somecommand that fails | cat -
    echo survived 
    

Recommandé pour l'utilisation - trap en sortie

Le verdict est le suivant: si vous voulez pouvoir gérer une erreur au lieu de quitter aveuglément, au lieu d'utiliser set -e, utilisez un trap sur le pseudo signal ERR.

Le piège ERR ne consiste pas à exécuter du code lorsque le shell lui-même se termine avec un code d'erreur différent de zéro, mais à toute commande exécutée par ce shell qui ne fait pas partie d'une condition (comme dans si cmd, ou cmd ||) quitte avec un statut de sortie différent de zéro.

En règle générale, nous définissons un gestionnaire d'interruption pour fournir des informations de débogage supplémentaires sur la ligne et la cause de la sortie. Rappelez-vous que le code de sortie de la dernière commande à l'origine du signal ERR serait toujours disponible à ce stade.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

et nous venons d'utiliser ce gestionnaire comme ci-dessous en plus du script qui échoue

trap cleanup ERR

En regroupant ces éléments dans un script simple contenant false, à la ligne 15, les informations que vous obtiendriez en tant que

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

Le paramètre trap fournit également des options, quelle que soit l’erreur, pour exécuter simplement le nettoyage à la fin du shell (par exemple, votre script shell se termine), sur le signal EXIT. Vous pouvez également intercepter plusieurs signaux en même temps. La liste des signaux supportés pour intercepter se trouve sur la page de manuel trap.1p - Linux

Une autre chose à noter serait de comprendre qu'aucune des méthodes fournies ne fonctionne si vous avez affaire à des sous-shells, et vous devrez peut-être ajouter votre propre traitement des erreurs.

  • Sur un sous-shell avec set -e ne fonctionnerait pas. Le paramètre false est limité au sous-shell et n'est jamais propagé au shell parent. Pour gérer les erreurs ici, ajoutez votre propre logique à faire (false) || false

    set -e
    (false)
    echo survived
    
  • La même chose arrive avec trap aussi. La logique ci-dessous ne fonctionnerait pas pour les raisons mentionnées ci-dessus.

    trap 'echo error' ERR
    (false)
    
5
Inian

Voici une simple interruption qui affiche le dernier argument de tout ce qui a échoué pour STDERR, signale la ligne sur laquelle elle a échoué et quitte le script avec le numéro de ligne comme code de sortie. Notez que ce ne sont pas toujours de bonnes idées, mais cela montre une application créative sur laquelle vous pourriez construire.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Je mets cela dans un script avec une boucle pour le tester. Je vérifie juste pour un coup sur quelques nombres aléatoires; vous pouvez utiliser des tests réels. Si j'ai besoin de renflouer, j'appelle faux (ce qui déclenche le piège) avec le message que je veux lancer.

Pour que la fonctionnalité soit élaborée, le piège appelle une fonction de traitement. Vous pouvez toujours utiliser une instruction case sur votre argument ($ _) si vous avez besoin de nettoyer davantage, etc. Attribuez à var une valeur pour un peu de sucre syntaxique -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case $x in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Exemple de sortie:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Évidemment, vous pourriez 

runTest1 "Test1 fails" # message not used if it succeeds

Beaucoup de place pour l'amélioration de la conception.

Les inconvénients incluent le fait que false n'est pas jolie (donc le sucre), et que d'autres choses qui déclenchent le piège peuvent paraître un peu stupides. Pourtant, j'aime cette méthode.

3
Paul Hodges

Je trouve souvent utile d'écrire une fonction pour gérer les messages d'erreur afin que le code soit plus propre.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Cela prend le code d'erreur de la commande précédente et l'utilise comme code d'erreur par défaut lors de la sortie de l'intégralité du script. Il note également l'heure, avec les microsecondes où elle est prise en charge (le %N de la date GNU est en nanosecondes, que nous tronçons à microsecondes plus tard).

Si la première option est zéro ou un entier positif, il devient le code de sortie et nous le supprimons de la liste des options. Nous signalons ensuite le message à l'erreur standard, avec le nom du script, le mot "ERREUR" et l'heure (nous utilisons le paramètre expansion pour tronquer des nanosecondes en microsecondes ou, pour des temps non GNU, pour tronquer, par exemple de 12:34:56.%N à 12:34:56). . Un signe deux-points et un espace sont ajoutés après le mot ERREUR, mais uniquement lorsqu'un message d'erreur est fourni. Enfin, nous quittons le script en utilisant le code de sortie déterminé précédemment, en déclenchant les interruptions normalement.

Quelques exemples (supposons que le code réside dans script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
2
Adam Katz

Vous avez 2 options: rediriger la sortie du script vers un fichier, introduire un fichier journal dans le script et 

  1. Rediriger la sortie vers un fichier :

Ici, vous supposez que le script génère toutes les informations nécessaires, y compris les messages d’avertissement et d’erreur. Vous pouvez ensuite rediriger la sortie vers un fichier de votre choix.

./runTests &> output.log

La commande ci-dessus redirige à la fois la sortie standard et la sortie d'erreur vers votre fichier journal.

En utilisant cette approche, vous n'avez pas besoin d'introduire un fichier journal dans le script. La logique est donc un peu plus simple.

  1. Introduisez un fichier journal dans le script :

Dans votre script, ajoutez un fichier journal en le codant:

logFile='./path/to/log/file.log'

ou en le passant par un paramètre:

logFile="${1}"  # This assumes the first parameter to the script is the log file

C'est une bonne idée d'ajouter l'horodatage au moment de l'exécution au fichier journal en haut du script:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Vous pouvez ensuite rediriger vos messages d'erreur vers le fichier journal.

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Cela ajoutera l'erreur au fichier journal et poursuivra l'exécution. Si vous souhaitez arrêter l'exécution lorsque des erreurs critiques se produisent, vous pouvez exit le script:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Notez que exit 1 indique que le programme arrête l'exécution en raison d'une erreur non spécifiée. Vous pouvez personnaliser cela si vous voulez.

En utilisant cette approche, vous pouvez personnaliser vos journaux et créer un fichier journal différent pour chaque composant de votre script.


Si vous avez un script relativement petit ou souhaitez exécuter le script de quelqu'un d'autre sans le modifier, la première approche est plus appropriée.

Si vous souhaitez toujours que le fichier journal se trouve au même emplacement, il s'agit de la meilleure option du fichier. En outre, si vous avez créé un gros script avec plusieurs composants, vous pouvez consigner chaque partie différemment et la seconde approche est votre seule option. 

0
alamoot