ShellCheck Configuration and Static Analysis
Comprehensive guidance for configuring and using ShellCheck to improve shell script quality, catch common pitfalls, and enforce best practices through static code analysis.
When to Use This Skill
-
Setting up linting for shell scripts in CI/CD pipelines
-
Analyzing existing shell scripts for issues
-
Understanding ShellCheck error codes and warnings
-
Configuring ShellCheck for specific project requirements
-
Integrating ShellCheck into development workflows
-
Suppressing false positives and configuring rule sets
-
Enforcing consistent code quality standards
-
Migrating scripts to meet quality gates
ShellCheck Fundamentals
What is ShellCheck?
ShellCheck is a static analysis tool that analyzes shell scripts and detects problematic patterns. It supports:
-
Bash, sh, dash, ksh, and other POSIX shells
-
Over 100 different warnings and errors
-
Configuration for target shell and flags
-
Integration with editors and CI/CD systems
Installation
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
Configuration Files
.shellcheckrc (Project Level)
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=SC2086
Environment Variables
Set default shell target
export SHELLCHECK_SHELL=bash
Enable strict mode
export SHELLCHECK_STRICT=true
Specify configuration file location
export SHELLCHECK_CONFIG=~/.shellcheckrc
Common ShellCheck Error Codes
SC1000-1099: Parser Errors
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
SC2000-2099: Shell Issues
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
SC2100-2199: Quoting Issues
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 )
SC3000-3999: POSIX Compliance Issues
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 }
Practical Configuration Examples
Minimal Configuration (Strict POSIX)
#!/bin/bash
Configure for maximum portability
shellcheck
--shell=sh
--external-sources
--check-sourced
script.sh
Development Configuration (Bash with Relaxed Rules)
#!/bin/bash
Configure for Bash development
shellcheck
--shell=bash
--exclude=SC1091,SC2119
--enable=all
script.sh
CI/CD Integration Configuration
#!/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
.shellcheckrc for Project
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=true
Integration Patterns
Pre-commit Hook Configuration
#!/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
GitHub Actions Workflow
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 {} \;
GitLab CI Pipeline
shellcheck: stage: lint image: koalaman/shellcheck-alpine script: - find . -type f -name "*.sh" -exec shellcheck {} ; allow_failure: false
Handling ShellCheck Violations
Suppressing Specific Warnings
#!/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
Common Violations and Fixes
SC2086: Double quote to prevent word splitting
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
SC2181: Check exit code directly
Problem
some_command if [ $? -eq 0 ]; then echo "success" fi
Solution
if some_command; then echo "success" fi
SC2015: Use if-then instead of && ||
Problem
[ -f "$file" ] && echo "exists" || echo "not found"
Solution - clearer intent
if [ -f "$file" ]; then echo "exists" else echo "not found" fi
SC2016: Expressions don't expand in single quotes
Problem
echo 'Variable value: $VAR'
Solution
echo "Variable value: $VAR"
SC2009: Use pgrep instead of grep
Problem
ps aux | grep -v grep | grep myprocess
Solution
pgrep -f myprocess
Performance Optimization
Checking Multiple Files
#!/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
Caching Results
#!/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
Output Formats
Default Format
shellcheck script.sh
Output:
script.sh:1:3: warning: foo is referenced but not assigned. [SC2154]
GCC Format (for CI/CD)
shellcheck --format=gcc script.sh
Output:
script.sh:1:3: warning: foo is referenced but not assigned.
JSON Format (for parsing)
shellcheck --format=json script.sh
Output:
[{"file": "script.sh", "line": 1, "column": 3, "level": "warning", "code": 2154, "message": "..."}]
Quiet Format
shellcheck --format=quiet script.sh
Returns non-zero if issues found, no output otherwise
Best Practices
-
Run ShellCheck in CI/CD - Catch issues before merging
-
Configure for your target shell - Don't analyze bash as sh
-
Document exclusions - Explain why violations are suppressed
-
Address violations - Don't just disable warnings
-
Enable strict mode - Use --enable=all with careful exclusions
-
Update regularly - Keep ShellCheck current for new checks
-
Use pre-commit hooks - Catch issues locally before pushing
-
Integrate with editors - Get real-time feedback during development
Resources
-
ShellCheck GitHub: https://github.com/koalaman/shellcheck
-
ShellCheck Wiki: https://www.shellcheck.net/wiki/
-
Error Code Reference: https://www.shellcheck.net/