Meilleure pratique pour utiliser $? en bash?

10

Quand j'ai lu cette réponse sur $? une autre question me vient à l'esprit.

Existe-t-il une meilleure pratique pour utiliser $? en bash?


Prenons un exemple:

Nous avons un script linéaire et je voudrais savoir que toute la commande a été exécutée correctement. Pensez-vous qu'il est correct d'appeler une petite fonction (appelons-la "did_it_work"), de vérifier le code d'erreur et de casser s'il ne l'est pas.

#!/bin/bash 

function did_it_work {
    code=$1
    if [ "$code" -ne "0" ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

dir=some/path

mkdir -p $dir
did_it_work $? 

cd $dir
did_it_work $? 

run_some_command
did_it_work $? 

Cette approche signifie bien sûr que je dois résoudre manuellement le problème s'il y en a et réexécuter le script.

Pensez-vous que c'est une bonne idée ou existe-t-il une autre meilleure pratique pour le faire?

/Merci

Johan
la source

Réponses:

15

Une façon courante est:

die() {
    IFS=' ' # make sure "$*" is joined with spaces

    # output the arguments if any on stderr:
    [ "$#" -eq 0 ] || printf '%s\n' "$*" 1>&2
    exit 1
}

alors vous l'utilisez comme ceci:

mkdir -p some/path || die "mkdir failed with status $?"

Ou si vous voulez qu'il inclue le statut de sortie, vous pouvez le changer en:

die() {
    last_exit_status=$?
    IFS=' '
    printf '%s\n' "FATAL ERROR: $* (status $last_exit_status)" 1>&2
    exit 1
}

puis l'utiliser est un peu plus simple:

mkdir -p some/path || die "mkdir failed"

En cas d'échec, vous mkdiraurez probablement déjà émis un message d'erreur, de sorte que le second peut être considéré comme redondant, et vous pouvez simplement faire:

mkdir -p some/path || exit   # with the same (failing) exit status as mkdir's
mkdir -p some/path || exit 1 # with exit status 1 always

(ou utilisez la première variante de dieci - dessus sans argument)

Juste au cas où vous ne l'auriez pas vu command1 || command2auparavant, il s'exécute command1et s'il command1échoue, il s'exécute command2.

Vous pouvez donc le lire comme "créer le répertoire ou mourir".

Votre exemple ressemblerait à:

mkdir -p some/path || die "mkdir failed"
cd some/path || die "cd failed"
some_command || die "some_command failed"

Ou vous pouvez aligner le diesplus à droite pour que le code principal soit plus évident.

mkdir -p some/path         || die "mkdir failed"
cd some/path               || die "cd failed"
some_command               || die "some_command failed"

Ou sur la ligne suivante lorsque les lignes de commande sont longues:

mkdir -p some/path ||
  die "mkdir failed"

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

De plus, si vous prévoyez d'utiliser le nom some/pathplusieurs fois, stockez-le dans une variable afin de ne pas avoir à le taper et pouvez facilement le modifier si vous en avez besoin. Et lorsque vous passez des arguments variables à des commandes, assurez-vous d'utiliser le --délimiteur d'options pour que l'argument ne soit pas pris comme option s'il commence par -.

dir=some/path
mkdir -p -- "$dir"         || die "Cannot make $dir"
cd -P -- "$dir"            || die "Cannot cd to $dir"
some_command               || die "Cannot run some_command"
Mikel
la source
9

Vous pouvez réécrire votre code comme ceci:

#!/bin/bash
function try {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "$1 did not work: exit status $code"
        exit 1
    fi
}

try mkdir -p some/path
try cd some/path
try run_some_command

Si vous n'avez pas réellement besoin de consigner le code d'erreur, mais simplement si la commande a réussi ou non, vous pouvez raccourcir try()davantage comme ceci:

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}
Warren Young
la source
Vous pouvez également utiliser le code dans ce format. <pre> function try {
BillThor
Si vous utilisez cette fonctionnalité en ligne et que vous ne voulez pas revenir du vrai côté, vous pouvez le remplacer return $?par la fonction :intégrée.
BillThor
8

Si vous voulez vraiment exitune erreur et utilisez Bash, alors vous devriez également considérer set -e. De help set:

-e Quitter immédiatement si une commande se termine avec un état différent de zéro.

Bien sûr, cela ne vous donne pas la flexibilité d'une fonction did_it_work (), mais c'est un moyen facile de vous assurer que votre script bash s'arrête sur une erreur sans ajouter beaucoup d'appels à votre nouvelle fonction.

Steven D
la source
set -eest utile. Certaines commandes renvoient des valeurs non nulles dans des circonstances normales (par exemple, diff). Lorsque j'utilise set -e dans un script où j'attends un retour différent de zéro, je le fais command || true.
Shawn J.Goff
2
De plus, même si vous utilisez set -e, vous pouvez définir un «gestionnaire d'exceptions» pour détecter toutes les erreurs trap did_it_work EXIT.
Gilles 'SO- arrête d'être méchant'
1
Cela fait partie de POSIX, pas une fonctionnalité spécifique à bash. Utilisez-le mais soyez conscient de certains pièges mywiki.wooledge.org/BashFAQ/105
kmkaplan
@ ShawnJ.Goff je préfère faire command && true. De cette façon, la valeur de retour n'est pas modifiée.
kmkaplan
@kmkaplan Votre dernier commentaire n'a aucun sens pour moi. Le but de command || trueest d'empêcher la set -esortie du script si commandrenvoie un code de sortie différent de zéro. Il modifie le code de sortie car nous en avons besoin. La seule chose à command && truefaire est de s'exécuter true(retourner un code de sortie zéro) si la commande `a réussi (a renvoyé un code de sortie zéro) - c'est un no-op complet.
tripleee