Interpretive guidance for shell script quality using shfmt and shellcheck. Use when linting shell files, configuring shell tools, troubleshooting shellcheck errors, or understanding formatting options.
/plugin marketplace add racurry/neat-little-package/plugin install mr-sparkle@neat-little-packageThis skill inherits all available tools. When active, it can use any tool Claude has access to.
default-editorconfigdefault-shellcheckrcshellcheck-reference.mdshfmt-reference.mdThis skill teaches how to apply shell script formatting and linting tools effectively using mr-sparkle's tool selection system. It provides guidance on what the tools do, how they work together, and common error patterns you'll encounter.
Claude knows how to use shfmt and shellcheck. Fetch these docs only when you need:
Reference URLs:
Key principle: Shell uses BOTH tools together - shfmt for formatting, shellcheck for linting. Unlike Python/JS which have alternative tool groups, shell scripts get both tools applied sequentially.
What this means:
Decision test: You don't choose between tools - both always run when available.
The linting system applies both tools to shell files:
Shell script detected (.sh, .bash, .zsh)
↓
Run: shfmt -w (format)
↓
Run: shellcheck (lint)
↓
Report results
Detection logic:
.sh, .bash, or .zsh.editorconfig, .shellcheckrc, .git, etc.)Tools run sequentially: shfmt modifies the file first, then shellcheck analyzes the result.
From official docs:
shfmt -w <file> (write changes).editorconfig file (EditorConfig standard)Common options:
-i <num> - Indent with spaces (0 = tabs)-bn - Binary ops like && and | may start a line-ci - Switch cases will be indented-sr - Redirect operators will be followed by a spaceConfiguration location:
.editorconfig in project root or parent directoriesWhen shfmt helps:
EditorConfig pattern for shell:
# .editorconfig
[*.sh]
indent_style = space
indent_size = 2
shfmt honors these EditorConfig properties:
indent_style (space or tab) → -i flagindent_size (number) → -i flagbinary_next_line → -bn flagswitch_case_indent → -ci flagspace_redirects → -sr flagSee default-editorconfig for recommended settings.
See shfmt-reference.md for detailed formatting examples and options.
From official docs:
shellcheck <file>.shellcheckrc file in project rootError code format: SC#### (e.g., SC2086, SC2034)
Common categories:
Configuration file format:
# .shellcheckrc
disable=SC2086,SC2154 # Comma-separated codes to ignore
shell=bash # Default shell to assume
When shellcheck catches issues:
Most valuable checks:
"$var")"$@" not $@ to prevent word splittingConfiguration strategy:
See shellcheck-reference.md for common error codes and fixes.
See default-shellcheckrc for recommended baseline configuration.
shfmt runs first:
shellcheck runs second:
Why this order: Formatting changes can affect what shellcheck sees (e.g., line continuation), so format first, then analyze.
shfmt auto-fixes:
shellcheck suggests but doesn't auto-fix:
Result: shfmt makes cosmetic changes automatically, shellcheck requires your attention.
What it means: Variable expansion could word-split or glob.
Problem:
file=$1
cat $file # SC2086: Double quote to prevent splitting
Fix:
file=$1
cat "$file" # Properly quoted
When to ignore: Intentional word splitting (rare, document it).
What it means: Variable assigned but never read.
Problem:
name="test"
# name is never used
echo "hello"
Fix: Remove the variable or actually use it.
When to ignore: Variables sourced by other scripts, exported for external use.
What it means: Variable used but never set in this script.
Problem:
echo "$USER_HOME" # SC2154: USER_HOME not assigned
Fix: Assign before use, or disable if it's from environment/sourced file.
Disable for specific variable:
# shellcheck disable=SC2154
echo "$USER_HOME" # Set by parent script
What it means: Command substitution needs quotes to prevent word splitting.
Problem:
rm $(find . -name "*.tmp") # SC2046: Quote to prevent splitting
Fix:
rm "$(find . -name "*.tmp")"
# Or better: use while read loop for many files
What it means: shellcheck can't find sourced file.
Problem:
source ./config.sh # SC1091: Not following
Fix: Either make file available or disable:
# shellcheck source=./config.sh
source ./config.sh
See shellcheck-reference.md for more error codes.
2-space indentation (recommended):
# .editorconfig
root = true
[*.sh]
indent_style = space
indent_size = 2
Tab indentation:
[*.{sh,bash}]
indent_style = tab
See default-editorconfig for complete example.
Start here:
# .shellcheckrc
# No configuration - use all defaults
Common relaxations:
# .shellcheckrc
# Allow source files that shellcheck can't find
disable=SC1091
# Target specific shell
shell=bash
Project-specific:
# .shellcheckrc
# Disable specific issues after review
disable=SC2086,SC2154 # Word splitting, external variables
# Target shell
shell=bash
See default-shellcheckrc for recommended starting point.
Problem: Dismissing SC2086 because "it works for me."
Why it fails:
file="my document.txt"
cat $file # Tries to cat "my" and "document.txt" separately
Better:
file="my document.txt"
cat "$file" # Treats as single argument
Rule: Always quote variable expansions unless you specifically want word splitting.
source for Config Files shellcheck Can't FindProblem: shellcheck warns about unfound sourced files.
source /etc/myapp/config # SC1091: Not following
Why it's noisy: shellcheck can't verify external files.
Better: Use directive to tell shellcheck about it:
# shellcheck source=/dev/null
source /etc/myapp/config
Or: If file is in repo:
# shellcheck source=./config/defaults.sh
source "${CONFIG_DIR}/defaults.sh"
Problem: Adding # shellcheck disable=SC2086,SC2046,SC2068... to every function.
Why it fails: Defeats purpose of linting - you're hiding real issues.
Better: Fix the code to pass checks, or carefully disable specific lines:
# This one case legitimately needs word splitting
# shellcheck disable=SC2086
eval $command_string
Philosophy: Disables should be rare and documented.
Problem: Manually reformatting code after shfmt runs.
Why it fails: Hook will reformat again, creating formatting loops.
Better: Configure shfmt to match your style via .editorconfig:
[*.sh]
indent_style = space
indent_size = 4 # If you prefer 4 spaces
binary_next_line = true # If you want &&/|| to start lines
Let the tool enforce consistency.
Problem: Blindly applying shellcheck suggestions without testing.
Why it fails: Some suggestions change behavior (quotes, command substitution).
Better: Test scripts after applying fixes:
# Before applying fixes
bash -n script.sh # Syntax check
./script.sh # Test execution
# After applying suggested fixes
bash -n script.sh # Verify syntax still valid
./script.sh # Verify behavior unchanged
The mr-sparkle plugin's linting hook:
.sh, .bash, .zsh)What this means: Formatting happens automatically, but you need to address shellcheck warnings manually.
Before finalizing shell scripts:
Auto-fixed by shfmt:
Requires manual attention (shellcheck):
The universal linting script handles shell files automatically:
# Lint shell script (applies fixes and checks)
./plugins/mr-sparkle/skills/linting/scripts/lint.py script.sh
# JSON output for programmatic use
./plugins/mr-sparkle/skills/linting/scripts/lint.py script.sh --format json
Exit codes:
0 - Clean or successfully formatted1 - shellcheck warnings found (non-blocking)2 - Tool execution errorSee linting skill for complete CLI documentation.
Detailed guides (linked from this skill for progressive disclosure):
shfmt-reference.md - Formatting options and EditorConfig integrationshellcheck-reference.md - Common error codes with examples and fixesdefault-editorconfig - Recommended shfmt configurationdefault-shellcheckrc - Recommended shellcheck baselineOfficial documentation to fetch:
Remember: This skill documents mr-sparkle's shell linting approach. Fetch official docs when you need specific error code explanations or advanced configuration options.