Linting Bash
Intégration de ShellCheck et shfmt pour l'assurance qualité des scripts bash.
ShellCheck
Installation
# Debian/Ubuntu
apt install shellcheck
# macOS
brew install shellcheck
# Depuis les sources
cabal update && cabal install ShellCheck
Utilisation de base
# Vérifier un seul fichier
shellcheck script.sh
# Vérifier plusieurs fichiers
shellcheck *.sh
# Avec un dialecte shell spécifique
shellcheck --shell=bash script.sh
shellcheck --shell=sh script.sh
# Exclure des règles spécifiques
shellcheck --exclude=SC2086 script.sh
shellcheck --exclude=SC2086,SC2046 script.sh
# Formats de sortie
shellcheck --format=gcc script.sh # Style GCC
shellcheck --format=json script.sh # JSON pour les outils
shellcheck --format=diff script.sh # Diff unifié
Codes ShellCheck courants
| Code | Problème | Correction |
|---|---|---|
| SC2086 | Guillemets manquants pour éviter le splitting | "$var" |
| SC2046 | Guillemets pour la substitution de commande | "$(cmd)" |
| SC2006 | Utiliser $() au lieu des backticks | $(cmd) |
| SC2034 | Variable semble inutilisée | Supprimer ou exporter |
| SC2155 | Déclarer et assigner séparément | Split local var; var=$(...) |
| SC2164 | Utiliser `cd ... | |
| SC2181 | Vérifier le code de sortie directement | if cmd; then |
| SC2129 | Envisager de regrouper les écritures | Utiliser { } > file |
| SC1090 | Impossible de suivre le fichier sourcé | Utiliser # shellcheck source=path |
| SC2154 | Variable référencée mais non assignée | Initialiser ou déclarer |
Directives ShellCheck
# Désactiver pour la ligne suivante
# shellcheck disable=SC2086
echo $unquoted_var
# Désactiver pour tout le fichier (en haut)
# shellcheck disable=SC2086,SC2046
# Spécifier le fichier source pour le sourcing
# shellcheck source=./lib/functions.sh
source "$SCRIPT_DIR/lib/functions.sh"
# Spécifier le dialecte shell
# shellcheck shell=bash
# Désactiver pour un bloc (non supporté - utiliser par ligne)
Modèles de directives en ligne
# Désactiver un avertissement spécifique avec explication
# shellcheck disable=SC2034 # Variable utilisée par le script source
readonly CONFIG_VERSION="1.0"
# Désactiver plusieurs codes
# shellcheck disable=SC2086,SC2046
result=$(echo $var)
# Directive source pour les chemins dynamiques
# shellcheck source=/dev/null
source "${DYNAMIC_PATH}/config.sh"
shfmt
Installation
# macOS
brew install shfmt
# Installation Go
go install mvdan.cc/sh/v3/cmd/shfmt@latest
# Snap
snap install shfmt
# Téléchargement binaire
# Depuis https://github.com/mvdan/sh/releases
Utilisation de base
# Formater et afficher sur stdout
shfmt script.sh
# Formater sur place
shfmt -w script.sh
# Vérifier le formatage (code de sortie 1 si non formaté)
shfmt -d script.sh
# Répertoire récursif
shfmt -w .
shfmt -w scripts/
Options de formatage
# Indentation
shfmt -i 2 script.sh # Indentation de 2 espaces
shfmt -i 4 script.sh # Indentation de 4 espaces
shfmt -i 0 script.sh # Tabulations (par défaut)
# Opérateurs binaires en début de ligne
shfmt -bn script.sh
# Indentation des cas switch
shfmt -ci script.sh
# Espace après les opérateurs de redirection
shfmt -sr script.sh
# Conserver l'alignement des colonnes
shfmt -kp script.sh
# Accolade d'ouverture de fonction sur une ligne séparée
shfmt -fn script.sh
# Combiné
shfmt -i 4 -ci -bn script.sh
Configuration (.editorconfig)
# .editorconfig
[*.sh]
indent_style = space
indent_size = 4
shell_variant = bash
binary_next_line = true
switch_case_indent = true
space_redirects = true
Exemples de transformations
Avant shfmt :
if [ -f "$file" ];then
echo "exists"
fi
for i in 1 2 3;do
process $i
done
Après shfmt -i 4 -ci :
if [ -f "$file" ]; then
echo "exists"
fi
for i in 1 2 3; do
process $i
done
Intégration Pre-commit
.pre-commit-config.yaml
repos:
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.9.0
hooks:
- id: shellcheck
args: ["--severity=warning"]
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.7.0-1
hooks:
- id: shfmt
args: ["-i", "4", "-ci", "-w"]
# Alternative : hooks locaux
- repo: local
hooks:
- id: shellcheck
name: shellcheck
entry: shellcheck
language: system
types: [shell]
args: ["--severity=warning", "-x"]
- id: shfmt
name: shfmt
entry: shfmt
language: system
types: [shell]
args: ["-i", "4", "-ci", "-w"]
Exécution de Pre-commit
# Installer les hooks
pre-commit install # ou : prek install
# Exécuter sur tous les fichiers
pre-commit run --all-files # ou : prek run --all-files
# Exécuter un hook spécifique
pre-commit run shellcheck --all-files
pre-commit run shfmt --all-files
# Exécuter sur des fichiers spécifiques
pre-commit run --files script.sh
Intégration CI/CD
GitHub Actions
name: Shell Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
severity: warning
- name: Check formatting with shfmt
uses: mvdan/github-action-shfmt@master
with:
flags: -d -i 4 -ci
GitLab CI
shellcheck:
image: koalaman/shellcheck-alpine:stable
script:
- find . -name "*.sh" -exec shellcheck {} +
shfmt:
image: mvdan/shfmt:latest
script:
- shfmt -d -i 4 -ci .
Correction des problèmes courants
SC2086 : Guillemets pour éviter le splitting
# Mauvais
echo $var
# Bon
echo "$var"
printf '%s\n' "$var"
SC2155 : Déclarer et assigner séparément
# Mauvais - masque le code de sortie
local var=$(some_command)
# Bon
local var
var=$(some_command)
SC2164 : Utiliser cd || exit
# Mauvais
cd "$dir"
rm -rf *
# Bon
cd "$dir" || exit 1
rm -rf *
# Ou avec un sous-shell
(cd "$dir" && rm -rf *)
SC2181 : Vérifier le code de sortie directement
# Mauvais
command
if [ $? -eq 0 ]; then
# Bon
if command; then
SC1090/SC1091 : Problèmes de source
# Ajouter une directive pour la source dynamique
# shellcheck source=/dev/null
source "$DYNAMIC_PATH/lib.sh"
# Ou spécifier le chemin réel
# shellcheck source=./lib/functions.sh
source "$SCRIPT_DIR/lib/functions.sh"
Intégration avec les éditeurs
VS Code
Installer l'extension "ShellCheck" de Timon Wong.
// settings.json
{
"shellcheck.enable": true,
"shellcheck.run": "onSave",
"shellcheck.executablePath": "shellcheck",
"editor.formatOnSave": true,
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
}
}
Vim/Neovim
" Avec ALE
let g:ale_linters = {'sh': ['shellcheck']}
let g:ale_fixers = {'sh': ['shfmt']}
let g:ale_sh_shfmt_options = '-i 4 -ci'
" Avec coc.nvim
" Installer l'extension coc-sh
Bonnes pratiques
- Exécutez shellcheck tôt - intégrez-le dans l'éditeur et le CI
- Corrigez les problèmes, ne les supprimez pas - ne désactivez que pour une bonne raison
- Documentez les suppressions - expliquez pourquoi la règle est désactivée
- Utilisez les niveaux de sévérité -
--severity=warningpour le CI - Formatage cohérent - utilisez shfmt dans pre-commit
- Verrouillez les versions des outils - épinglez les versions dans CI/pre-commit