Un modèle pour démarrer un script shell
J'ai régulièrement le besoin d'écrire des scripts shell un peu évolués. Il y a quelques mois, j'ai commencé à travailler sur un modèle que je met régulièrement à jour. Aujourd'hui, j'ai travaillé sur la couche pour gérer les logs et c'est pourquoi j'écris cet article.
Ce modèle contient les fonctions usage
et help
pour décrire le fonctionnement du programme. La fonction on_interrupt
permet de gérer le signal SIGINT
quand le programme est interrompu.
La fonction main
est la fonction qui contient le code principal du script. Elle contient la gestion des paramètres passés au script. Par défaut, plusieurs y sont déjà intégrés : -l
pour définir le niveau de log désiré et -h
pour afficher l'aide.
Enfin, il y a la fonction log
pour afficher des messages de debug avec différents niveaux de criticité. Cela permet de générer des messages qui seront affichés ou pas selon les besoins. Les messages sont colorés selon le niveau de criticité et ils sont redirigés vers stderr
. Il est également possible d'ajouter la date.
Le code source est disponible sur Gitnet et comme toujours, vous pouvez en faire ce que vous voulez !
En conclusion, le développement de ce modèle de script shell a été un projet passionnant et fructueux. En l'enrichissant régulièrement, j'ai pu créer une base solide pour gérer efficacement les fonctionnalités essentielles d'un script.

Murph v1.20 is out! 📣
Une nouvelle version de Murph a été publiée le 27 juillet 2023 🥳 Murph est un framework ope…

Capture, un reverse proxy pour analyser les requêtes de vos applications
Capture est un reverse proxy HTTP qui se place entre votre application cliente et une API. C…

Gitea et forgejo v1.19.0 sont dans les bacs 🥳
Le 20 mars dernier a été publiée la version v1.19.0 de Gitea ! Forgejo, le soft fork de Gite…
Une manière de mettre en forme et d'automatiser les fonctions help() et usage()/synopsis() : En entête de chaque script :
#!/bin/bash ################# #_# Usage : {script} [-{options}] serviceName1 <serviceName2 ...> #_# Description : Stop a service list #_# by first checking if the services are already started #_# Options #_# -h : Display short help (usage synopsis) #_# -H : Display full help #_# -A : Stop all active services #_# -K : Stop all known services #_# -p <value> : Use <value> profile to stop a list of services #_# -P : Display list all available profiles #_# Example use : {script} accounting #_# {script} -p profile1 ################# # Script options options=hHAKp:P ############### # Description : # Give the name of current script without .sh extension # Globals: # None # Arguments: # None # Outputs: # name of current script without .sh extension # Return: # 0 ############### name() { basename "$0" .sh } ############### # Description : # Print script usage text # Globals: # $0 # options = options string defined in calling script # Arguments: # None # Outputs: # Script usage text # Explain: # - Each calling script must start with a multilines comment block where # each line to be printed as usage text is prefixed by '#_#' mark # - awk extract those lines from the calling script shell code # - sed remove the '#_# ' prefix from those extracted lines (with ONE space character after the mark !) # - If the usage text contains some {script} marks, then sed replace them by script's name without .sh suffix # - If the usage text contains some {options} marks, then sed replace them by $options global variable value defined in calling script # - Output the resulting lines as usage text # Return: # 0 ############### usage() { awk '/^#_# /{print}' "$0" | sed -e 's/^#_# //' -e "s/{script}/$(name)/" -e "s/{options}/${options:?}/" } ############### # Description : # Print script usage text first line # with spaces before : removed # Globals: # usage # Arguments: # None # Outputs: # Script usage text first line # with spaces before : removed ## Return: # 0 ############### synopsis() { usage | head -n 1 | sed -e 's/[[:space:]]*:/:/' } ############### # Description : # Call synopsis() or usage() & exit script, # if -h or -H option is used # Globals: # displayHelpSynopsis # displayHelpFull # ok # Arguments: # $1 = displayHelp value # Outputs: # synopsis() output when -h option is used # usage() output when -H option is used # Return: # 0 ############### displayUsage() { ( ([[ $1 -eq ${displayHelpSynopsis:?} ]] && synopsis) || ([[ $1 -eq ${displayHelpFull:?} ]] && usage) ) && exit "${ok:?}" } # Initial script options ok=0 displayHelp=0 displayHelpSynopsis=1 displayHelpFull=2 displayProfiles=0 stopAllActives=0 stopAllKnown=0 # Read user options while getopts ":$options" option; do case $option in h) displayHelp=${displayHelpSynopsis:?};; H) displayHelp=${displayHelpFull:?};; A) stopAllActives=1;; K) stopAllKnown=1;; p) setProfile "$OPTARG";; P) displayProfiles=1;; :) display error "${errorMsgMissingArgument:?}: -$OPTARG" exit "${errorMissingArgument:?}";; *) display error "${errorMsgUnknownOption:?}: -$OPTARG" exit "${errorUnknownOption:?}";; esac done shift $((OPTIND-1)) # Execute terminal actions displayUsage "$displayHelp" echo uTest # Use test.sh -h and test.sh -H to see the difference (synopsis vs help)
Intéressant mais depuis que j'utilise Python pour faire mes scripts, je ne vois plus l'intérêt de bash. Le langage et l'écosystème me semble plus adapté à la création de commandes.
Le seul cas que je vois que justifierais encore l'usage de bash est de s'exécuter sur un serveur qui n'a pas Python.
Ok, c'était pour répondre dans le langage qui était le sujet de ce post. Maintenant pour python et gérer les options de la ligne de commande facilement, il y a click : https://pypi.org/project/click/
Pourquoi ne pas ajouter -o pipefail ? https://wizardzines.com/comics/bash-errors/
Car le script est en shell et non en bash.
● 15:37 simon@endurance ~ % echo $SHELL /bin/zsh ● 15:37 simon@endurance ~ % set -o pipefail ● 15:37 simon@endurance ~ % bash simon@endurance:~$ set -o pipefail simon@endurance:~$ sh $ set -o pipefail sh: 1: set: Illegal option -o pipefail