Bash-Linting
Integration von ShellCheck und shfmt für die Qualitätssicherung von Bash-Skripten.
ShellCheck
Installation
# Debian/Ubuntu
apt install shellcheck
# macOS
brew install shellcheck
# Von Quelle
cabal update && cabal install ShellCheck
Grundlegende Verwendung
# Einzelne Datei prüfen
shellcheck script.sh
# Mehrere Dateien prüfen
shellcheck *.sh
# Mit spezifischem Shell-Dialekt
shellcheck --shell=bash script.sh
shellcheck --shell=sh script.sh
# Bestimmte Regeln ausschließen
shellcheck --exclude=SC2086 script.sh
shellcheck --exclude=SC2086,SC2046 script.sh
# Ausgabeformate
shellcheck --format=gcc script.sh # GCC-Stil
shellcheck --format=json script.sh # JSON für Tooling
shellcheck --format=diff script.sh # Unified-Diff
Häufige ShellCheck-Codes
| Code | Problem | Lösung |
|---|---|---|
| SC2086 | Doppelte Anführungszeichen fehlen (Globbing/Splitting) | "$var" |
| SC2046 | Befehlssubstitution in Anführungszeichen setzen | "$(cmd)" |
| SC2006 | $() statt Backticks verwenden | $(cmd) |
| SC2034 | Variable scheint ungenutzt | Entfernen oder exportieren |
| SC2155 | Deklaration und Zuweisung trennen | local var; var=$(...) aufteilen |
| SC2164 | `cd ... | |
| SC2181 | Exit-Status direkt prüfen | if cmd; then |
| SC2129 | Schreibvorgänge gruppieren | { } > datei |
| SC1090 | Quelldatei kann nicht verfolgt werden | # shellcheck source=pfad |
| SC2154 | Variable referenziert, aber nicht zugewiesen | Initialisieren oder deklarieren |
ShellCheck-Direktiven
# Für die nächste Zeile deaktivieren
# shellcheck disable=SC2086
echo $unquoted_var
# Für die gesamte Datei deaktivieren (am Anfang)
# shellcheck disable=SC2086,SC2046
# Quelldatei für Source angeben
# shellcheck source=./lib/functions.sh
source "$SCRIPT_DIR/lib/functions.sh"
# Shell-Dialekt angeben
# shellcheck shell=bash
# Block deaktivieren (nicht unterstützt – zeilenweise verwenden)
Inline-Direktiven-Muster
# Spezifische Warnung mit Erklärung deaktivieren
# shellcheck disable=SC2034 # Variable wird vom Source-Skript verwendet
readonly CONFIG_VERSION="1.0"
# Mehrere Codes deaktivieren
# shellcheck disable=SC2086,SC2046
result=$(echo $var)
# Source-Direktive für dynamische Pfade
# shellcheck source=/dev/null
source "${DYNAMIC_PATH}/config.sh"
shfmt
Installation
# macOS
brew install shfmt
# Go install
go install mvdan.cc/sh/v3/cmd/shfmt@latest
# Snap
snap install shfmt
# Binär-Download
# Von https://github.com/mvdan/sh/releases
Grundlegende Verwendung
# Formatieren und auf stdout ausgeben
shfmt script.sh
# Direkt in der Datei formatieren
shfmt -w script.sh
# Formatierung prüfen (Exit 1, wenn unformatiert)
shfmt -d script.sh
# Rekursiv im Verzeichnis
shfmt -w .
shfmt -w scripts/
Formatierungsoptionen
# Einrückung
shfmt -i 2 script.sh # 2 Leerzeichen Einzug
shfmt -i 4 script.sh # 4 Leerzeichen Einzug
shfmt -i 0 script.sh # Tabs (Standard)
# Binäre Operatoren am Zeilenanfang
shfmt -bn script.sh
# Case-Ausdrücke eingerückt
shfmt -ci script.sh
# Umleitungsoperatoren mit Leerzeichen
shfmt -sr script.sh
# Spaltenausrichtung beibehalten
shfmt -kp script.sh
# Funktionsöffnende Klammer in neuer Zeile
shfmt -fn script.sh
# Kombiniert
shfmt -i 4 -ci -bn script.sh
Konfiguration (.editorconfig)
# .editorconfig
[*.sh]
indent_style = space
indent_size = 4
shell_variant = bash
binary_next_line = true
switch_case_indent = true
space_redirects = true
Beispieltransformationen
Vor shfmt:
if [ -f "$file" ];then
echo "exists"
fi
for i in 1 2 3;do
process $i
done
Nach shfmt -i 4 -ci:
if [ -f "$file" ]; then
echo "exists"
fi
for i in 1 2 3; do
process $i
done
Pre-Commit-Integration
.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"]
# Alternativ: lokale Hooks
- 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"]
Pre-Commit ausführen
# Hooks installieren
pre-commit install # oder: prek install
# Auf allen Dateien ausführen
pre-commit run --all-files # oder: prek run --all-files
# Spezifischen Hook ausführen
pre-commit run shellcheck --all-files
pre-commit run shfmt --all-files
# Auf bestimmten Dateien ausführen
pre-commit run --files script.sh
Integration in CI/CD
GitHub Actions
name: Shell Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ShellCheck ausführen
uses: ludeeus/action-shellcheck@master
with:
severity: warning
- name: Formatierung mit shfmt prüfen
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 .
Häufige Probleme beheben
SC2086: Anführungszeichen setzen
# Schlecht
echo $var
# Gut
echo "$var"
printf '%s\n' "$var"
SC2155: Deklaration und Zuweisung trennen
# Schlecht – maskiert Exit-Status
local var=$(some_command)
# Gut
local var
var=$(some_command)
SC2164: cd verwenden || exit
# Schlecht
cd "$dir"
rm -rf *
# Gut
cd "$dir" || exit 1
rm -rf *
# Oder mit Subshell
(cd "$dir" && rm -rf *)
SC2181: Exit-Status direkt prüfen
# Schlecht
command
if [ $? -eq 0 ]; then
# Gut
if command; then
SC1090/SC1091: Source-Probleme
# Direktive für dynamische Source hinzufügen
# shellcheck source=/dev/null
source "$DYNAMIC_PATH/lib.sh"
# Oder tatsächlichen Pfad angeben
# shellcheck source=./lib/functions.sh
source "$SCRIPT_DIR/lib/functions.sh"
Editor-Integration
VS Code
Installieren Sie die Erweiterung "ShellCheck" von Timon Wong.
// settings.json
{
"shellcheck.enable": true,
"shellcheck.run": "onSave",
"shellcheck.executablePath": "shellcheck",
"editor.formatOnSave": true,
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
}
}
Vim/Neovim
" Mit ALE
let g:ale_linters = {'sh': ['shellcheck']}
let g:ale_fixers = {'sh': ['shfmt']}
let g:ale_sh_shfmt_options = '-i 4 -ci'
" Mit coc.nvim
" Installiere coc-sh-Erweiterung
Best Practices
- ShellCheck früh ausführen – in Editor und CI integrieren
- Probleme beheben, nicht unterdrücken – nur mit gutem Grund deaktivieren
- Unterdrückungen dokumentieren – erklären, warum eine Regel deaktiviert ist
- Schweregrade verwenden –
--severity=warningfür CI - Konsistente Formatierung – shfmt im Pre-Commit verwenden
- Tools versionieren – Versionen in CI/Pre-Commit festlegen