Comment trouver le numéro de ligne dans Bash lorsqu'une erreur s'est produite?

21

Comment trouvez-vous le numéro de ligne dans Bash où une erreur s'est produite?

Exemple

Je crée le script simple suivant avec des numéros de ligne pour expliquer ce dont nous avons besoin. Le script copiera les fichiers de

cp $file1 $file2
cp $file3 $file4

Lorsque l'une des cpcommandes échoue, la fonction se termine avec la sortie 1 . Nous voulons ajouter la possibilité à la fonction d'imprimer également l'erreur avec le numéro de ligne (par exemple, 8 ou 12).

Est-ce possible?

Exemple de script

1 #!/bin/bash
2
3
4 function in_case_fail {
5 [[ $1 -ne 0 ]] && echo "fail on $2" && exit 1
6 }
7
8 cp $file1 $file2
9 in_case_fail $? "cp $file1 $file2"
10
11
12 cp $file3 $file4
13 in_case_fail $? "cp $file3 $file4"
14
Yael
la source
Vous pouvez utiliser set -xet / ou set -vpour retracer ce qui a été exécuté. Pas exactement ce que vous avez demandé, mais ce sera probablement utile aussi.
Rolf

Réponses:

29

Plutôt que d'utiliser votre fonction, j'utiliserais plutôt cette méthode:

$ cat yael.bash
#!/bin/bash

set -eE -o functrace

file1=f1
file2=f2
file3=f3
file4=f4

failure() {
  local lineno=$1
  local msg=$2
  echo "Failed at $lineno: $msg"
}
trap 'failure ${LINENO} "$BASH_COMMAND"' ERR

cp -- "$file1" "$file2"
cp -- "$file3" "$file4"

Cela fonctionne en interceptant ERR puis en appelant la failure()fonction avec la commande numéro de ligne + bash actuelle qui a été exécutée.

Exemple

Ici , je l' ai pas pris de soin de créer les fichiers, f1, f2, f3ou f4. Lorsque j'exécute le script ci-dessus:

$ ./yael.bash
cp: cannot stat f1’: No such file or directory
Failed at 17: cp -- "$file1" "$file2"

Il échoue, signalant le numéro de ligne plus la commande qui a été exécutée.

slm
la source
14

En plus de LINENOcontenir le numéro de ligne actuel, il existe les tableaux BASH_LINENOand FUNCNAME(et BASH_SOURCE) qui contiennent les noms de fonction et les numéros de ligne à partir desquels ils sont appelés.

Vous pouvez donc faire quelque chose comme ceci:

#!/bin/bash

error() {
        printf "'%s' failed with exit code %d in function '%s' at line %d.\n" "${1-something}" "$?" "${FUNCNAME[1]}" "${BASH_LINENO[0]}"
}

foo() {
        ( exit   0 ) || error "this thing"
        ( exit 123 ) || error "that thing"
}

foo

Courir qui imprimerait

'that thing' failed with exit code 123 in function 'foo' at line 9.

Si vous utilisez set -e, ou trap ... ERRpour détecter automatiquement des erreurs, notez qu'elles comportent des mises en garde. Il est également plus difficile d'inclure une description de ce que le script faisait à l'époque (comme vous l'avez fait dans votre exemple), bien que cela puisse être plus utile pour un utilisateur régulier que le simple numéro de ligne.

Voir par exemple ceux-ci pour les problèmes avec set -eet d'autres:

ilkkachu
la source
13

Bash a une variable intégrée $LINENOqui est remplacée par le numéro de ligne actuel dans une instruction, vous pouvez donc faire

in_case_fail $? "at $LINENO: cp $file1 $file2"

Vous pouvez également essayer d'utiliser les trap ... ERRexécutions lorsqu'une commande échoue (si le résultat n'est pas testé). Par exemple:

trap 'rc=$?; echo "error code $rc at $LINENO"; exit $rc' ERR

Ensuite, si une commande comme cp $file1 $file2échoue, vous obtiendrez le message d'erreur avec le numéro de ligne et une sortie. Vous trouverez également la commande en erreur dans la variable $BASH_COMMAND(mais pas de redirection, etc.).

meuh
la source