Il y a des choses intéressantes, mais il manque ce qui me semble le plus important pour toute idée de défensivité en (Ba)SH : le quoting. Il faut grosso modo quoter toutes les variables, sauf cas particuliers. Par exemple, le snippet tout simple utilisé pour obtenir PROGNAME
a un comportement probablement inattendu si $0 contient des espaces :
# avec un script "/tmp/foo bar.bash" :
$ bash foo\ bar.bash
foo
# avec un script "/tmp/a b c d.bash" :
$ bash a\ b\ c\ d.bash
basename: extra operand ‘c’
Try 'basename --help' for more information.
# ouch.
Tout ça est dû au fait que sans quoting, l'expansion des variables est sujette au découpage de mots et l'expansion des chemins (pour le fun, essayer avec un script nommé *
), ce qui est en général (très) dangereux. La solution est simple : quoter toutes les subsitutions (ce qui inclus $()
), et par exemple le PROGNAME
devient readonly PROGNAME=$(basename "$0")
. On note ici la seule exception : l'assignation à une variable, qu'il n'est pas nécessaire de quoter, mais je recommanderais de toujours quoter, même ça, par ce que ça protège par exemple d'une erreur de refactoring.
Aussi comme le mentionne Sky dans son commentaire (au milieu du reste), utiliser set -e
(avorter le script si une commande dont le retour n'est pas vérifié échoue) est une excellente pratique qui évite une cascade de problèmes lorsque une commande échoue de façon inattendue, que je recommande fortement pour tout nouveau script.
À noter cependant que ça ne marche pas dans tous les cas qu'on pourrait attendre, par exemple foo=$(false)
ne terminera pas le script (car la commande est considérée comme vérifiée). J'ai l'habitude d'avoir une fonction du style die() { echo "$@">&2 ; exit 1; }
et de vérifier également ce type d'assignation en utilisant foo=$(false) || die "failed to do something"
.