Configuration et Analyse Statique avec ShellCheck
Guide complet pour configurer et utiliser ShellCheck afin d'améliorer la qualité des scripts shell, détecter les pièges courants et appliquer les bonnes pratiques par l'analyse statique du code.
Quand utiliser ce skill
- Configurer le linting des scripts shell dans les pipelines CI/CD
- Analyser les scripts shell existants pour détecter les problèmes
- Comprendre les codes d'erreur et avertissements de ShellCheck
- Configurer ShellCheck selon les exigences du projet
- Intégrer ShellCheck dans les workflows de développement
- Supprimer les faux positifs et configurer les ensembles de règles
- Appliquer des standards de qualité de code cohérents
- Migrer les scripts pour respecter les critères de qualité
Principes Fondamentaux de ShellCheck
Qu'est-ce que ShellCheck ?
ShellCheck est un outil d'analyse statique qui examine les scripts shell et détecte les motifs problématiques. Il supporte :
- Bash, sh, dash, ksh et autres shells POSIX
- Plus de 100 avertissements et erreurs différents
- Configuration pour le shell cible et ses flags
- Intégration avec les éditeurs et systèmes CI/CD
Installation
# macOS avec Homebrew
brew install shellcheck
# Ubuntu/Debian
apt-get install shellcheck
# Depuis les sources
git clone https://github.com/koalaman/shellcheck.git
cd shellcheck
make build
make install
# Vérifier l'installation
shellcheck --version
Fichiers de Configuration
.shellcheckrc (Niveau Projet)
Créez .shellcheckrc à la racine de votre projet :
# Spécifier le shell cible
shell=bash
# Activer les vérifications optionnelles
enable=avoid-nullary-conditions
enable=require-variable-braces
# Désactiver les avertissements spécifiques
disable=SC1091
disable=SC2086
Variables d'Environnement
# Définir le shell cible par défaut
export SHELLCHECK_SHELL=bash
# Activer le mode strict
export SHELLCHECK_STRICT=true
# Spécifier l'emplacement du fichier de configuration
export SHELLCHECK_CONFIG=~/.shellcheckrc
Codes d'Erreur ShellCheck Courants
SC1000-1099 : Erreurs d'Analyse
# SC1004: Continuation avec antislash non suivie d'une nouvelle ligne
echo hello\
world # Erreur - nécessite une continuation de ligne
# SC1008: Données invalides pour l'opérateur `=='
if [[ $var = "value" ]]; then # Espace avant ==
true
fi
SC2000-2099 : Problèmes Shell
# SC2009: Envisager d'utiliser pgrep ou pidof au lieu de grep|grep
ps aux | grep -v grep | grep myprocess # Utiliser pgrep à la place
# SC2012: Utiliser `ls` uniquement pour l'affichage. Utiliser `find` pour une sortie fiable
for file in $(ls -la) # Mieux : utiliser find ou globbing
# SC2015: Éviter d'utiliser && et || au lieu de if-then-else
[[ -f "$file" ]] && echo "found" || echo "not found" # Moins clair
# SC2016: Les expressions ne se développent pas entre guillemets simples
echo '$VAR' # Littéral $VAR, pas développement de variable
# SC2026: Ce mot n'est pas standard. Définir POSIXLY_CORRECT
# lors de l'utilisation avec des scripts pour d'autres shells
SC2100-2199 : Problèmes de Guillemets
# SC2086: Ajouter des guillemets doubles pour éviter le globbing et le fractionnement de mots
for i in $list; do # Devrait être : for i in $list ou for i in "$list"
echo "$i"
done
# SC2115: Tilde littéral dans le chemin non développé. Utiliser $HOME à la place
~/.bashrc # Dans les chaînes, utiliser "$HOME/.bashrc"
# SC2181: Vérifier le code de sortie directement avec `if`, pas indirectement dans une liste
some_command
if [ $? -eq 0 ]; then # Mieux : if some_command; then
# SC2206: Ajouter des guillemets pour éviter le fractionnement de mots ou définir IFS
array=( $items ) # Devrait utiliser : array=( $items )
SC3000-3999 : Problèmes de Conformité POSIX
# SC3010: Dans POSIX sh, utiliser 'case' au lieu de 'cond && foo'
[[ $var == "value" ]] && do_something # Pas POSIX
# SC3043: Dans POSIX sh, 'local' n'est pas défini
function my_func() {
local var=value # Pas POSIX dans certains shells
}
Exemples de Configuration Pratiques
Configuration Minimale (POSIX Strict)
#!/bin/bash
# Configurer pour la portabilité maximale
shellcheck \
--shell=sh \
--external-sources \
--check-sourced \
script.sh
Configuration de Développement (Bash avec Règles Relaxées)
#!/bin/bash
# Configurer pour le développement Bash
shellcheck \
--shell=bash \
--exclude=SC1091,SC2119 \
--enable=all \
script.sh
Configuration d'Intégration CI/CD
#!/bin/bash
set -Eeuo pipefail
# Analyser tous les scripts shell et échouer en cas de problème
find . -type f -name "*.sh" | while read -r script; do
echo "Checking: $script"
shellcheck \
--shell=bash \
--format=gcc \
--exclude=SC1091 \
"$script" || exit 1
done
.shellcheckrc pour Projet
# Dialecte shell à analyser
shell=bash
# Activer les vérifications optionnelles
enable=avoid-nullary-conditions,require-variable-braces,check-unassigned-uppercase
# Désactiver les avertissements spécifiques
# SC1091: Ne pas suivre les fichiers sourcés (nombreux faux positifs)
disable=SC1091
# SC2119: Utiliser function_name au lieu de function_name -- (arguments)
disable=SC2119
# Fichiers externes à sourcer pour le contexte
external-sources=true
Modèles d'Intégration
Configuration de Hook Pre-commit
#!/bin/bash
# .git/hooks/pre-commit
#!/bin/bash
set -e
# Trouver tous les scripts shell modifiés dans ce commit
git diff --cached --name-only | grep '\.sh$' | while read -r script; do
echo "Linting: $script"
if ! shellcheck "$script"; then
echo "ShellCheck failed on $script"
exit 1
fi
done
Workflow GitHub Actions
name: ShellCheck
on: [push, pull_request]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run ShellCheck
run: |
sudo apt-get install shellcheck
find . -type f -name "*.sh" -exec shellcheck {} \;
Pipeline GitLab CI
shellcheck:
stage: lint
image: koalaman/shellcheck-alpine
script:
- find . -type f -name "*.sh" -exec shellcheck {} \;
allow_failure: false
Gérer les Violations ShellCheck
Supprimer les Avertissements Spécifiques
#!/bin/bash
# Désactiver l'avertissement pour toute la ligne
# shellcheck disable=SC2086
for file in $(ls -la); do
echo "$file"
done
# Désactiver pour tout le script
# shellcheck disable=SC1091,SC2119
# Désactiver plusieurs avertissements (le format varie)
command_that_fails() {
# shellcheck disable=SC2015
[ -f "$1" ] && echo "found" || echo "not found"
}
# Désactiver la vérification spécifique pour la directive source
# shellcheck source=./helper.sh
source helper.sh
Violations Courantes et Corrections
SC2086 : Ajouter des guillemets doubles pour éviter le fractionnement de mots
# Problème
for i in $list; do done
# Solution
for i in $list; do done # Si $list est déjà entre guillemets, ou
for i in "${list[@]}"; do done # Si list est un tableau
SC2181 : Vérifier le code de sortie directement
# Problème
some_command
if [ $? -eq 0 ]; then
echo "success"
fi
# Solution
if some_command; then
echo "success"
fi
SC2015 : Utiliser if-then au lieu de && ||
# Problème
[ -f "$file" ] && echo "exists" || echo "not found"
# Solution - intention plus claire
if [ -f "$file" ]; then
echo "exists"
else
echo "not found"
fi
SC2016 : Les expressions ne se développent pas entre guillemets simples
# Problème
echo 'Variable value: $VAR'
# Solution
echo "Variable value: $VAR"
SC2009 : Utiliser pgrep au lieu de grep
# Problème
ps aux | grep -v grep | grep myprocess
# Solution
pgrep -f myprocess
Optimisation des Performances
Vérifier Plusieurs Fichiers
#!/bin/bash
# Vérification séquentielle
for script in *.sh; do
shellcheck "$script"
done
# Vérification parallèle (plus rapide)
find . -name "*.sh" -print0 | \
xargs -0 -P 4 -n 1 shellcheck
Mise en Cache des Résultats
#!/bin/bash
CACHE_DIR=".shellcheck_cache"
mkdir -p "$CACHE_DIR"
check_script() {
local script="$1"
local hash
local cache_file
hash=$(sha256sum "$script" | cut -d' ' -f1)
cache_file="$CACHE_DIR/$hash"
if [[ ! -f "$cache_file" ]]; then
if shellcheck "$script" > "$cache_file" 2>&1; then
touch "$cache_file.ok"
else
return 1
fi
fi
[[ -f "$cache_file.ok" ]]
}
find . -name "*.sh" | while read -r script; do
check_script "$script" || exit 1
done
Formats de Sortie
Format par Défaut
shellcheck script.sh
# Sortie :
# script.sh:1:3: warning: foo is referenced but not assigned. [SC2154]
Format GCC (pour CI/CD)
shellcheck --format=gcc script.sh
# Sortie :
# script.sh:1:3: warning: foo is referenced but not assigned.
Format JSON (pour l'analyse)
shellcheck --format=json script.sh
# Sortie :
# [{"file": "script.sh", "line": 1, "column": 3, "level": "warning", "code": 2154, "message": "..."}]
Format Silencieux
shellcheck --format=quiet script.sh
# Retourne un code non-zéro si des problèmes sont trouvés, pas de sortie sinon
Bonnes Pratiques
- Exécuter ShellCheck dans CI/CD - Détecter les problèmes avant la fusion
- Configurer pour votre shell cible - Ne pas analyser bash comme sh
- Documenter les exclusions - Expliquer pourquoi les violations sont supprimées
- Résoudre les violations - Ne pas simplement désactiver les avertissements
- Activer le mode strict - Utiliser
--enable=allavec exclusions soigneuses - Mettre à jour régulièrement - Maintenir ShellCheck à jour pour les nouvelles vérifications
- Utiliser les hooks pre-commit - Détecter les problèmes localement avant de pousser
- Intégrer avec les éditeurs - Obtenir un retour en temps réel pendant le développement