Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Master ShellCheck static analysis configuration and usage for shell script quality. Use when setting up linting infrastructure, fixing code issues, or ensuring script portability.
.claude/skills/microck-shellcheck-configuration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 478% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 213% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 129% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 66% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 177% | 0% |
Comprehensive guidance for configuring and using ShellCheck to improve shell script quality, catch common pitfalls, and enforce best practices through static code analysis.
ShellCheck is a static analysis tool that analyzes shell scripts and detects problematic patterns. It supports:
bash# macOS with Homebrew brew install shellcheck # Ubuntu/Debian apt-get install shellcheck # From source git clone https://github.com/koalaman/shellcheck.git cd shellcheck make build make install # Verify installation shellcheck --version
Create .shellcheckrc in your project root:
# Specify target shell
shell=bash
# Enable optional checks
enable=avoid-nullary-conditions
enable=require-variable-braces
# Disable specific warnings
disable=SC1091
disable=SC2086bash# Set default shell target export SHELLCHECK_SHELL=bash # Enable strict mode export SHELLCHECK_STRICT=true # Specify configuration file location export SHELLCHECK_CONFIG=~/.shellcheckrc
bash# SC1004: Backslash continuation not followed by newline echo hello\ world # Error - needs line continuation # SC1008: Invalid data for operator `==' if [[ $var = "value" ]]; then # Space before == true fi
bash# SC2009: Consider using pgrep or pidof instead of grep|grep ps aux | grep -v grep | grep myprocess # Use pgrep instead # SC2012: Use `ls` only for viewing. Use `find` for reliable output for file in $(ls -la) # Better: use find or globbing # SC2015: Avoid using && and || instead of if-then-else [[ -f "$file" ]] && echo "found" || echo "not found" # Less clear # SC2016: Expressions don't expand in single quotes echo '$VAR' # Literal $VAR, not variable expansion # SC2026: This word is non-standard. Set POSIXLY_CORRECT # when using with scripts for other shells
bash# SC2086: Double quote to prevent globbing and word splitting for i in $list; do # Should be: for i in $list or for i in "$list" echo "$i" done # SC2115: Literal tilde in path not expanded. Use $HOME instead ~/.bashrc # In strings, use "$HOME/.bashrc" # SC2181: Check exit code directly with `if`, not indirectly in a list some_command if [ $? -eq 0 ]; then # Better: if some_command; then # SC2206: Quote to prevent word splitting or set IFS array=( $items ) # Should use: array=( $items )
bash# SC3010: In POSIX sh, use 'case' instead of 'cond && foo' [[ $var == "value" ]] && do_something # Not POSIX # SC3043: In POSIX sh, use 'local' is undefined function my_func() { local var=value # Not POSIX in some shells }
bash#!/bin/bash # Configure for maximum portability shellcheck \ --shell=sh \ --external-sources \ --check-sourced \ script.sh
bash#!/bin/bash # Configure for Bash development shellcheck \ --shell=bash \ --exclude=SC1091,SC2119 \ --enable=all \ script.sh
bash#!/bin/bash set -Eeuo pipefail # Analyze all shell scripts and fail on issues find . -type f -name "*.sh" | while read -r script; do echo "Checking: $script" shellcheck \ --shell=bash \ --format=gcc \ --exclude=SC1091 \ "$script" || exit 1 done
# Shell dialect to analyze against
shell=bash
# Enable optional checks
enable=avoid-nullary-conditions,require-variable-braces,check-unassigned-uppercase
# Disable specific warnings
# SC1091: Not following sourced files (many false positives)
disable=SC1091
# SC2119: Use function_name instead of function_name -- (arguments)
disable=SC2119
# External files to source for context
external-sources=truebash#!/bin/bash # .git/hooks/pre-commit #!/bin/bash set -e # Find all shell scripts changed in this 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
yamlname: 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 {} \;
yamlshellcheck: stage: lint image: koalaman/shellcheck-alpine script: - find . -type f -name "*.sh" -exec shellcheck {} \; allow_failure: false
bash#!/bin/bash # Disable warning for entire line # shellcheck disable=SC2086 for file in $(ls -la); do echo "$file" done # Disable for entire script # shellcheck disable=SC1091,SC2119 # Disable multiple warnings (format varies) command_that_fails() { # shellcheck disable=SC2015 [ -f "$1" ] && echo "found" || echo "not found" } # Disable specific check for source directive # shellcheck source=./helper.sh source helper.sh
bash# Problem for i in $list; do done # Solution for i in $list; do done # If $list is already quoted, or for i in "${list[@]}"; do done # If list is an array
bash# Problem some_command if [ $? -eq 0 ]; then echo "success" fi # Solution if some_command; then echo "success" fi
bash# Problem [ -f "$file" ] && echo "exists" || echo "not found" # Solution - clearer intent if [ -f "$file" ]; then echo "exists" else echo "not found" fi
bash# Problem echo 'Variable value: $VAR' # Solution echo "Variable value: $VAR"
bash# Problem ps aux | grep -v grep | grep myprocess # Solution pgrep -f myprocess
bash#!/bin/bash # Sequential checking for script in *.sh; do shellcheck "$script" done # Parallel checking (faster) find . -name "*.sh" -print0 | \ xargs -0 -P 4 -n 1 shellcheck
bash#!/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
bashshellcheck script.sh # Output: # script.sh:1:3: warning: foo is referenced but not assigned. [SC2154]
bashshellcheck --format=gcc script.sh # Output: # script.sh:1:3: warning: foo is referenced but not assigned.
bashshellcheck --format=json script.sh # Output: # [{"file": "script.sh", "line": 1, "column": 3, "level": "warning", "code": 2154, "message": "..."}]
bashshellcheck --format=quiet script.sh # Returns non-zero if issues found, no output otherwise
--enable=all with careful exclusions| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 8,941 | 2,417 | -73% | 1 | 1 | 0% | 1,492 | 3,066 | +105% | 0 | 0 | — |
case-02 | fail→fail | 7,275 | 4,586 | -37% | 1 | 1 | 0% | 1,295 | 3,407 | +163% | 0 | 0 | — |
case-03 | pass→pass | 2,698 | 3,852 | +43% | 1 | 1 | 0% | 434 | 3,310 | +663% | 0 | 0 | — |
case-04 | pass→pass | 5,024 | 3,356 | -33% | 1 | 1 | 0% | 838 | 3,167 | +278% | 0 | 0 | — |
case-05 | pass→pass | 7,270 | 4,082 | -44% | 1 | 1 | 0% | 1,236 | 3,306 | +167% | 0 | 0 | — |
case-06 | fail→pass | 3,501 | 3,655 | +4% | 1 | 1 | 0% | 568 | 3,282 | +478% | 0 | 0 | — |
case-07 | pass→pass | 3,256 | 3,429 | +5% | 1 | 1 | 0% | 506 | 3,207 | +534% | 0 | 0 | — |
case-08 | pass→pass | 3,092 | 3,418 | +11% | 1 | 1 | 0% | 500 | 3,332 | +566% | 0 | 0 | — |
case-09 | pass→pass | 3,332 | 3,100 | -7% | 1 | 1 | 0% | 513 | 3,098 | +504% | 0 | 0 | — |
case-10 | pass→pass | 3,348 | 2,624 | -22% | 1 | 1 | 0% | 421 | 3,124 | +642% | 0 | 0 | — |
case-11 | fail→pass | 6,524 | 3,648 | -44% | 1 | 1 | 0% | 1,081 | 3,380 | +213% | 0 | 0 | — |
case-12 | pass→fail | 8,957 | 7,607 | -15% | 1 | 1 | 0% | 1,513 | 4,088 | +170% | 0 | 0 | — |
case-13 | pass→pass | 3,232 | 4,073 | +26% | 1 | 1 | 0% | 574 | 3,385 | +490% | 0 | 0 | — |
case-14 | pass→pass | 3,020 | 3,204 | +6% | 1 | 1 | 0% | 483 | 3,176 | +558% | 0 | 0 | — |
case-15 | pass→pass | 6,997 | 5,824 | -17% | 1 | 1 | 0% | 1,280 | 3,697 | +189% | 0 | 0 | — |
case-16 | pass→pass | 2,799 | 2,878 | +3% | 1 | 1 | 0% | 438 | 3,143 | +618% | 0 | 0 | — |
case-17 | fail→pass | 10,575 | 8,206 | -22% | 1 | 1 | 0% | 1,752 | 4,006 | +129% | 0 | 0 | — |
case-18 | pass→pass | 3,268 | 3,306 | +1% | 1 | 1 | 0% | 492 | 3,304 | +572% | 0 | 0 | — |
case-19 | fail→pass | 12,415 | 3,367 | -73% | 1 | 1 | 0% | 1,963 | 3,257 | +66% | 0 | 0 | — |
case-20 | fail→pass | 8,028 | 6,056 | -25% | 1 | 1 | 0% | 1,326 | 3,674 | +177% | 0 | 0 | — |
case-21 | pass→pass | 10,212 | 7,795 | -24% | 1 | 1 | 0% | 1,666 | 4,143 | +149% | 0 | 0 | — |
case-22 | pass→pass | 13,441 | 14,585 | +9% | 1 | 1 | 0% | 2,629 | 5,315 | +102% | 0 | 0 | — |
case-23 | pass→pass | 15,358 | 16,505 | +7% | 1 | 1 | 0% | 2,641 | 5,662 | +114% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.