shellcheck-configuration
Master ShellCheck static analysis configuration and usage for shell script quality. Use when setting up linting infrastructure, fixing code issues, or ensuring script portability.
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.
Do not use this skill when
Instructions
resources/implementation-playbook.md.Use this skill when
ShellCheck Fundamentals
What is ShellCheck?
ShellCheck is a static analysis tool that analyzes shell scripts and detects problematic patterns. It supports:
Installation
# macOS with Homebrew
brew install shellcheckUbuntu/Debian
apt-get install shellcheckFrom source
git clone https://github.com/koalaman/shellcheck.git
cd shellcheck
make build
make installVerify installation
shellcheck --versionConfiguration Files
.shellcheckrc (Project Level)
Create .shellcheckrc in your project root:
# Specify target shell
shell=bashEnable optional checks
enable=avoid-nullary-conditions
enable=require-variable-bracesDisable specific warnings
disable=SC1091
disable=SC2086Environment Variables
# Set default shell target
export SHELLCHECK_SHELL=bashEnable strict mode
export SHELLCHECK_STRICT=trueSpecify configuration file location
export SHELLCHECK_CONFIG=~/.shellcheckrcCommon ShellCheck Error Codes
SC1000-1099: Parser Errors
# SC1004: Backslash continuation not followed by newline
echo hello\
world # Error - needs line continuationSC1008: Invalid data for operator =='
if [[ $var = "value" ]]; then # Space before ==
true
fiSC2000-2099: Shell Issues
<div class="overflow-x-auto my-6"><table class="min-w-full divide-y divide-border border border-border"><thead><tr><th class="px-4 py-2 text-left text-sm font-semibold text-foreground bg-muted/50"># SC2009: Consider using pgrep or pidof instead of grep</th><th class="px-4 py-2 text-left text-sm font-semibold text-foreground bg-muted/50">grep</th></tr></thead><tbody class="divide-y divide-border"></tbody></table></div>lsSC2012: Use
only for viewing. Usefindfor reliable output
for file in $(ls -la) # Better: use find or globbing<div class="overflow-x-auto my-6"><table class="min-w-full divide-y divide-border border border-border"><thead><tr><th class="px-4 py-2 text-left text-sm font-semibold text-foreground bg-muted/50"># SC2015: Avoid using && and</th><th class="px-4 py-2 text-left text-sm font-semibold text-foreground bg-muted/50">instead of if-then-else</th></tr></thead><tbody class="divide-y divide-border"></tbody></table></div>
SC2016: Expressions don't expand in single quotes
echo '$VAR' # Literal $VAR, not variable expansionSC2026: 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 splittingif
for i in $list; do # Should be: for i in $list or for i in "$list"
echo "$i"
doneSC2115: Literal tilde in path not expanded. Use $HOME instead
~/.bashrc # In strings, use "$HOME/.bashrc"SC2181: Check exit code directly with
, not indirectly in a list
some_command
if [ $? -eq 0 ]; then # Better: if some_command; thenSC2206: 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 POSIXSC3043: 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 pipefailAnalyze 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=bashEnable optional checks
enable=avoid-nullary-conditions,require-variable-braces,check-unassigned-uppercaseDisable specific warnings
SC1091: Not following sourced files (many false positives)
disable=SC1091SC2119: Use function_name instead of function_name -- (arguments)
disable=SC2119External files to source for context
external-sources=trueIntegration 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: ShellCheckon: [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: falseHandling ShellCheck Violations
Suppressing Specific Warnings
#!/bin/bashDisable warning for entire line
shellcheck disable=SC2086
for file in $(ls -la); do
echo "$file"
doneDisable 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.shCommon Violations and Fixes
SC2086: Double quote to prevent word splitting
# Problem
for i in $list; do doneSolution
for i in $list; do done # If $list is already quoted, or
for i in "${list[@]}"; do done # If list is an arraySC2181: Check exit code directly
# Problem
some_command
if [ $? -eq 0 ]; then
echo "success"
fiSolution
if some_command; then
echo "success"
fiSC2015: 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"
fiSC2016: 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 myprocessSolution
pgrep -f myprocessPerformance Optimization
Checking Multiple Files
#!/bin/bashSequential checking
for script in .sh; do
shellcheck "$script"
doneParallel checking (faster)
find . -name ".sh" -print0 | \
xargs -0 -P 4 -n 1 shellcheckCaching Results
#!/bin/bashCACHE_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" ]]
}
<div class="overflow-x-auto my-6"><table class="min-w-full divide-y divide-border border border-border"><thead><tr><th class="px-4 py-2 text-left text-sm font-semibold text-foreground bg-muted/50">find . -name ".sh"</th><th class="px-4 py-2 text-left text-sm font-semibold text-foreground bg-muted/50">while read -r script; do</th></tr></thead><tbody class="divide-y divide-border"></tbody></table></div>
done
Output Formats
Default Format
shellcheck script.shOutput:
script.sh:1:3: warning: foo is referenced but not assigned. [SC2154]
GCC Format (for CI/CD)
shellcheck --format=gcc script.shOutput:
script.sh:1:3: warning: foo is referenced but not assigned.
JSON Format (for parsing)
shellcheck --format=json script.shOutput:
[{"file": "script.sh", "line": 1, "column": 3, "level": "warning", "code": 2154, "message": "..."}]
Quiet Format
shellcheck --format=quiet script.shReturns non-zero if issues found, no output otherwise