cheat sheet
shellcheck
Catch quoting bugs, missing checks, and POSIX portability mistakes in shell scripts. Covers every flag, severity levels, inline directives, CI/pre-commit integration, and the most common rules.
shellcheck — Shell Script Linter
What it is
shellcheck is a static analysis tool for shell scripts, written in Haskell by Vidar Holen and the open-source community since 2012. It parses sh, bash, dash, and ksh scripts and emits warnings for quoting bugs, undefined variables, ignored exit codes, POSIX violations, and a long list of other footguns that would otherwise blow up at runtime. Reach for shellcheck on every shell script you write — wire it into your editor, pre-commit hook, and CI. There's no real alternative; it's effectively the standard linter for the language.
Install
ShellCheck is packaged everywhere — distro repos, Homebrew, snap, scoop, even a Docker image. The standalone binary is also a single statically linked file you can drop onto any Linux host.
# Debian/Ubuntu
sudo apt install shellcheck
# RHEL/Fedora
sudo dnf install ShellCheck
# macOS
brew install shellcheck
# Arch
sudo pacman -S shellcheck
# Standalone binary
wget -qO- https://github.com/koalaman/shellcheck/releases/latest/download/shellcheck-stable.linux.x86_64.tar.xz \
| tar -xJ --strip-components=1 -C /usr/local/bin shellcheck-stable/shellcheck
# Docker
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable myscript.sh
# Verify
shellcheck --version
Output:
ShellCheck - shell script analysis tool
version: 0.11.0
license: GNU General Public License, version 3
website: https://www.shellcheck.net
v0.11.0 (Aug 2025) added several SC codes (SC2327–SC2332, SC3062), a new optional
avoid-negated-conditionscheck (SC2335), and disabled SC2002 ("useless use ofcat") by default. v0.10.0 (Mar 2024) introduced--rcfile, theextended-analysisdirective, and BusyBoxshsupport.
Syntax
Pass one or more script paths; ShellCheck infers the dialect from the shebang or the file extension. Exit code is non-zero if any issue is found.
shellcheck [OPTIONS] FILE [FILE...]
shellcheck - # read script from stdin
find . -name '*.sh' -exec shellcheck {} +
Output: (none — exits 0 on success)
Essential options
| Flag | Meaning |
|---|---|
-s SHELL | Force dialect: sh, bash, dash, ksh, bats |
-S LEVEL | Minimum severity to report: error, warning, info, style |
-e CODES | Exclude specific rules (-e SC2086,SC2155) |
-i CODES | Only include these rules (whitelist) |
-o CHECKS | Enable optional checks (-o all recommended) |
-f FORMAT | Output format: tty (default), gcc, checkstyle, json, json1, diff, quiet |
-x | Follow source / . directives across files |
-a | Check all files in sourced trees |
-P PATH | Source-path: additional dirs to search for source targets |
-W N | Max wiki-link width (cosmetic) |
-V | Print version and exit |
--severity=LEVEL | Long form of -S |
--enable=CHECKS | Long form of -o |
--list-optional | Show available optional checks |
--rcfile=PATH | Use a specific .shellcheckrc instead of auto-discovery (v0.10.0+) |
--norc | Skip .shellcheckrc discovery entirely (v0.10.0+) |
--extended-analysis=BOOL | Toggle dataflow-based checks like SC2317; equivalent to the extended-analysis directive |
A first run
The default output uses ANSI colours, highlights the offending line, points at the problem column, and lists the SCxxxx rule code with a one-line explanation and a wiki URL.
cat > greet.sh <<'EOF'
#!/usr/bin/env bash
name=$1
echo Hello $name, the time is `date`
EOF
shellcheck greet.sh
Output:
In greet.sh line 2:
name=$1
^-- SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean:
name="$1"
In greet.sh line 3:
echo Hello $name, the time is `date`
^---^ SC2086 (info): Double quote to prevent globbing and word splitting.
^---^ SC2006 (style): Use $(...) notation instead of legacy backticks `...`.
For more information:
https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbi...
https://www.shellcheck.net/wiki/SC2006 -- Use $(...) notation instead o...
Severity levels
Every finding is one of four severities. Use -S to raise the floor — -S warning mutes info and style messages, which is helpful when triaging a legacy script.
| Level | What it means |
|---|---|
error | The script is almost certainly broken (syntax, undefined refs) |
warning | The script will likely misbehave in some inputs (quoting, globbing) |
info | Idiomatic improvement; rarely outright wrong |
style | Cosmetic — backticks vs $(...), [ ] vs [[ ]], etc. |
shellcheck -S error deploy.sh # only show real bugs
shellcheck -S warning deploy.sh # include quoting issues
shellcheck -S style deploy.sh # the maximalist run (default)
Output:
# -S error on a clean script:
(no output)
Inline directives
Drop a # shellcheck comment to silence, configure, or scope a check. Directives apply to the next statement, the rest of the file, or the function they live in, depending on placement.
#!/usr/bin/env bash
# shellcheck shell=bash # force the dialect for ambiguous files
# Disable one rule for the whole file (must be near the top)
# shellcheck disable=SC2086
# Disable a rule for just the next statement
# shellcheck disable=SC2046
files=$(find . -type f)
# Disable multiple rules
# shellcheck disable=SC2086,SC2155
export PATH=/usr/local/bin:$PATH
# Enable an optional check globally
# shellcheck enable=require-variable-braces
# Tell shellcheck about a sourced file it can't follow
# shellcheck source=./lib/util.sh
source "$LIB/util.sh"
Output: (none — exits 0 on success)
Use disables sparingly — if you're disabling a rule on every script, it's a sign the rule is correct and your codebase has a real bug class to fix.
Optional (off-by-default) checks
shellcheck -o all turns on every optional check. The list is small but valuable: add-default-case, avoid-nullary-conditions, avoid-negated-conditions (v0.11.0+), quote-safe-variables, require-variable-braces, require-double-brackets, useless-use-of-cat (off by default since v0.11.0), and a few more. Run --list-optional to see them all.
shellcheck --list-optional
shellcheck -o all deploy.sh
shellcheck -o require-double-brackets,quote-safe-variables deploy.sh
Output (--list-optional):
name: add-default-case
default: disabled
help: Suggest adding a default case in `case` statements
name: avoid-negated-conditions
default: disabled
help: Suggest replacing `[ ! a -eq b ]` with `[ a -ne b ]` (SC2335)
name: avoid-nullary-conditions
default: disabled
help: Suggest explicitly using -n in `[ $var ]`
name: check-extra-masked-returns
default: disabled
help: Check for additional cases where exit codes are masked
name: check-set-e-suppressed
default: disabled
help: Notify when set -e is suppressed during function invocation
name: check-unassigned-uppercase
default: disabled
help: Warn when uppercase variables are unassigned
name: deprecate-which
default: disabled
help: Suggest 'command -v' instead of 'which'
name: quote-safe-variables
default: disabled
help: Suggest quoting variables without metacharacters
name: require-double-brackets
default: disabled
help: Require [[ and warn about [ ]
name: require-variable-braces
default: disabled
help: Require {} on every variable reference
name: useless-use-of-cat
default: disabled
help: Suggest piping/redirecting input instead of using cat (was SC2002 default until v0.11.0)
Project-wide configuration
A .shellcheckrc sets defaults for every invocation. ShellCheck walks upward from each script's directory and merges every .shellcheckrc it finds, so a repo-root file applies to every script under it and a lib/.shellcheckrc can override settings just for that subtree. As a last resort it also checks $XDG_CONFIG_HOME/shellcheckrc and $HOME/.shellcheckrc. Use this for team-wide rule choices instead of plastering disables across files.
# .shellcheckrc
shell=bash
severity=warning
enable=quote-safe-variables
enable=require-variable-braces
enable=avoid-negated-conditions # v0.11.0+ (SC2335)
disable=SC2034 # variable assigned but not used (we use indirection)
source-path=SCRIPTDIR
source-path=SCRIPTDIR/lib
external-sources=true
extended-analysis=true # dataflow analysis (SC2317 unreachable, etc.)
Output: (none — exits 0 on success)
# Override discovery: point at a specific config
shellcheck --rcfile=./ci/shellcheckrc greet.sh
# Or bypass any discovered .shellcheckrc entirely
shellcheck --norc greet.sh
# Auto-discovery (default): walks parent dirs, then $XDG_CONFIG_HOME, then $HOME
shellcheck greet.sh
Output: (none — exits 0 on success)
Available directives
Every directive below can live in a .shellcheckrc (one per line, key=value) or as an inline # shellcheck key=value comment.
| Directive | Purpose |
|---|---|
shell=sh|bash|dash|ksh|bats | Force dialect when there's no shebang |
severity=error|warning|info|style | Minimum severity to report |
enable=CHECK[,CHECK…] | Turn on an optional check (or keyphrase like all) |
disable=SC####[,SC####…] | Suppress one or more rules; supports SC2000-SC2099 ranges |
external-sources=true|false | Permit -x follow-through to sourced files |
source=PATH | Tell ShellCheck where a non-constant source resolves |
source-path=DIR | Search path for sourced files (SCRIPTDIR = script's own dir) |
extended-analysis=true|false | Toggle dataflow-based checks (SC2317 unreachable, return-value tracing) |
Following source and .
By default ShellCheck warns when it can't analyse a sourced file (SC1091). -x (or external-sources=true in .shellcheckrc) opens those files and analyses them in context, catching cross-file mistakes like a function used here but defined nowhere.
shellcheck -x deploy.sh
# Tell it where to look for sourced files
shellcheck -P ./lib:./common -x deploy.sh
# Or per-source hint
cat deploy.sh
# #!/usr/bin/env bash
# # shellcheck source=lib/util.sh
# source "$LIB_DIR/util.sh"
Output:
In deploy.sh line 4:
source "$LIB_DIR/util.sh"
^---------------^ SC1091 (info): Not following: $LIB_DIR/util.sh: openBinaryFile: does not exist (No such file or directory)
Output formats for tooling
-f switches to a machine-readable format so editors, CI, and review bots can attach findings to lines.
shellcheck -f gcc deploy.sh # editor-friendly: file:line:col:level: msg
shellcheck -f checkstyle deploy.sh # Jenkins / static-analysis dashboards
shellcheck -f json deploy.sh # array of objects
shellcheck -f json1 deploy.sh # one object with "comments": [...]
shellcheck -f diff deploy.sh # unified diff with suggested fixes
shellcheck -f quiet deploy.sh # no output, just exit code
Output (-f gcc):
deploy.sh:5:8: note: Double quote to prevent globbing and word splitting. [SC2086]
deploy.sh:9:1: warning: Tabs and spaces in indentation. [SC1107]
Output (-f json1 — abridged):
{
"comments": [
{
"file": "deploy.sh",
"line": 5,
"endLine": 5,
"column": 8,
"endColumn": 12,
"level": "info",
"code": 2086,
"message": "Double quote to prevent globbing and word splitting.",
"fix": {
"replacements": [
{"line":5,"column":8,"endLine":5,"endColumn":12,
"precedence":7,"insertionPoint":"afterEnd","replacement":"\"$NAME\""}
]
}
}
]
}
Output (-f diff):
--- a/deploy.sh
+++ b/deploy.sh
@@ -2,3 +2,3 @@
-name=$1
-echo Hello $name, the time is `date`
+name="$1"
+echo Hello "$name", the time is "$(date)"
Applying suggested fixes automatically
The diff format is a real unified diff you can pipe straight into patch. For larger codebases this is the fastest way to apply the safe quoting fixes.
shellcheck -f diff deploy.sh | patch -p1
git diff # review what changed
git restore deploy.sh # if you don't like it
Output:
patching file deploy.sh
Most-hit rules — the top ten
A short tour of the rules you'll see daily. The number in parentheses is roughly how often each shows up in real codebases.
SC2086 — Double quote to prevent globbing and word splitting
Variables and command substitutions undergo word splitting and pathname expansion unless quoted. This is the most common bug class in shell scripts.
# Bad
for f in $files; do rm $f; done
# Good
for f in "${files[@]}"; do rm -- "$f"; done
Output:
SC2086 (info): Double quote to prevent globbing and word splitting.
SC2155 — Declare and assign separately
local, export, readonly, and declare set an exit code of their own, masking the exit code of the command on the right side.
# Bad — failure of `date` is hidden
local now=$(date -u +%FT%TZ)
# Good
local now
now=$(date -u +%FT%TZ) || return
Output:
SC2155 (warning): Declare and assign separately to avoid masking return values.
SC2046 — Quote to prevent word splitting in command substitution
$(...) and `...` expand and then word-split unless quoted.
# Bad
rm $(find . -name '*.log')
# Good
find . -name '*.log' -delete
# or, when you really need a list:
find . -name '*.log' -print0 | xargs -0 rm --
Output:
SC2046 (warning): Quote this to prevent word splitting.
SC2181 — Check exit code directly, not via $?
if [ $? -ne 0 ] is verbose, fragile, and wrong if any other command runs between.
# Bad
make build
if [ $? -ne 0 ]; then echo "failed"; exit 1; fi
# Good
if ! make build; then echo "failed"; exit 1; fi
Output:
SC2181 (style): Check exit code directly with e.g. `if mycmd;`, not indirectly with $?.
SC2164 — cd may fail; check it
A cd that fails leaves you in the wrong directory; the rest of the script then runs against the original cwd.
# Bad
cd /var/log
rm *.gz # wrong if cd failed
# Good
cd /var/log || exit
rm /var/log/*.gz # or be explicit about the path
Output:
SC2164 (warning): Use 'cd ... || exit' or similar to handle cd failures.
SC2068 — Double quote array expansions
$@ and $* (and similarly ${arr[@]}) must be quoted to preserve element boundaries.
# Bad
mycmd $@
# Good
mycmd "$@"
Output:
SC2068 (error): Double quote array expansions to avoid re-splitting elements.
SC2034 — Variable appears unused
ShellCheck didn't see the variable read anywhere. False positives are common when you use indirect references (${!name}); silence with # shellcheck disable=SC2034 or add an underscore prefix.
# False-positive case (genuine indirection)
ENV_PROD=prod
ENV_STAGE=stage
key=PROD
# shellcheck disable=SC2034
echo "${!key}"
Output:
SC2034 (warning): ENV_PROD appears unused. Verify use (or export if used externally).
SC1090 / SC1091 — Can't follow non-constant source
source $LIB is opaque to ShellCheck unless you hint where to look.
# shellcheck source=lib/common.sh
source "$LIB_DIR/common.sh"
Output:
SC1090 (warning): ShellCheck can't follow non-constant source. Use a directive to specify location.
SC2006 — Use $(...) instead of backticks
Backticks nest poorly and look like apostrophes.
# Bad
ver=`git describe --tags`
# Good
ver=$(git describe --tags)
Output:
SC2006 (style): Use $(...) notation instead of legacy backticks `...`.
SC2207 — Avoid array=( $(cmd) )
That pattern word-splits, glob-expands, and loses any element with a space.
# Bad
files=( $(ls *.txt) )
# Good (bash 4+)
mapfile -t files < <(ls -1 *.txt)
# Or POSIX-ish
readarray -t files < <(find . -maxdepth 1 -name '*.txt')
Output:
SC2207 (warning): Prefer mapfile or read -a to split command output (or quote to avoid splitting).
Newer rules (v0.10.0 and v0.11.0)
ShellCheck keeps adding rules; here are the ones you're most likely to hit on a recently-upgraded toolchain. The big behavioural change in v0.11.0 is that SC2002 ("useless use of cat") was demoted to an opt-in optional check — long-standing complaints that it caused more noise than insight finally won.
SC2324 — x+=1 appends to a string, not increments
In Bash, += on an unset or string variable concatenates. To increment a number use (( x++ )) or (( x += 1 )).
# Bad — produces the string "01", not the number 1
x=0
x+=1
# Good
x=0
(( x += 1 ))
Output:
SC2324 (warning): x+=1 will append, not increment. Use (( x += 1 )) or x=$((x + 1)).
SC2327 / SC2328 — Don't capture the output of a redirected command
var=$(cmd > file) redirects stdout to the file and captures the (now-empty) stream into var. Almost always a bug.
# Bad
log=$(make build > build.log)
# Good — pick one
log=$(make build); printf '%s\n' "$log" > build.log
make build > build.log
Output:
SC2327 (warning): This command redirects its standard output, so the assignment will be empty.
SC2329 — Function is never invoked
ShellCheck now flags functions that are defined but never called anywhere in the script (or its -x-followed sources). Indirect callers ("$func", traps, complete -F) need a # shellcheck disable=SC2329.
# Bad
cleanup() { rm -rf "$tmp"; }
# (no caller anywhere)
# Good
cleanup() { rm -rf "$tmp"; }
trap cleanup EXIT
Output:
SC2329 (info): This function is never invoked. Verify name, or make it a static analysis comment.
SC2331 — Use -e, not unary -a
[ -a file ] is a non-portable alias for [ -e file ] and collides with the binary AND operator.
# Bad
[ -a "$path" ] && echo "exists"
# Good
[ -e "$path" ] && echo "exists"
Output:
SC2331 (warning): Use -e instead of unary -a (which is bashism and not POSIX).
SC2002 — Useless use of cat (now opt-in)
Demoted to optional in v0.11.0. Re-enable with enable=useless-use-of-cat if you still want it.
# .shellcheckrc
enable=useless-use-of-cat
Output: (none — exits 0 on success)
Exit codes
0 no issues found
1 issues found at requested severity
2 fatal error (file not found, parse error)
3 bad options / usage error
shellcheck deploy.sh && echo "clean"
echo "exit=$?"
Output:
clean
exit=0
Editor and shell integration
Most editors have a ShellCheck plugin that runs on save and surfaces findings in the gutter. The basic incantations:
# Vim — via ALE
# .vimrc
let g:ale_linters = {'sh': ['shellcheck']}
# Neovim — via nvim-lint
require('lint').linters_by_ft = { sh = {'shellcheck'}, bash = {'shellcheck'} }
# VS Code: install "ShellCheck" extension by Timon Wong
# Emacs — via flycheck
(setq flycheck-shellcheck-supported-shells '(bash dash ksh sh))
# fish completion
shellcheck --version # uses fish's hosted completion
Output: (none — exits 0 on success)
Pre-commit hook
A pre-commit hook catches issues before the script ever lands in the repo. The Python pre-commit framework has a ready-made hook; the manual .git/hooks/pre-commit version is below.
# .pre-commit-config.yaml — using the pre-commit framework
repos:
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.11.0
hooks:
- id: shellcheck
args: [--severity=warning, --external-sources]
Output: (none — exits 0 on success)
# Plain .git/hooks/pre-commit (executable)
#!/usr/bin/env bash
set -e
mapfile -t files < <(git diff --cached --name-only --diff-filter=ACM \
| grep -E '\.(sh|bash)$' || true)
[ "${#files[@]}" -eq 0 ] && exit 0
shellcheck -x -S warning "${files[@]}"
Output: (none — exits 0 on success)
GitHub Actions
A two-step job that runs ShellCheck on every push and surfaces inline annotations in the PR diff.
# .github/workflows/shellcheck.yml
cat > .github/workflows/shellcheck.yml <<'YAML'
name: shellcheck
on: [push, pull_request]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ludeeus/action-shellcheck@2.0.0
with:
severity: warning
additional_files: 'install setup'
ignore_paths: vendor third_party
YAML
Output: (none — exits 0 on success)
GitLab CI
shellcheck:
image: koalaman/shellcheck-alpine:stable
script:
- find . -type f \( -name '*.sh' -o -name '*.bash' \) -print0 \
| xargs -0 shellcheck -x -S warning
Output: (none — exits 0 on success)
Common pitfalls
- Disabling rules globally. If you find yourself adding
disable=SC2086to every file, the bug is in your codebase — fix the quoting instead. - Pinning to
latestin CI. New ShellCheck versions add rules; pin to a specific tag (v0.10.0) and bump it on purpose so CI stays reproducible. - Missing shebang. Without
#!/usr/bin/env bash(or equivalent), ShellCheck assumesshand floods you with POSIX-portability warnings. Add the shebang or# shellcheck shell=bash. - Treating SC2034 as gospel. Indirect variable refs (
${!var}) and variables exported for child processes look unused to ShellCheck. Disable per-line, not project-wide. - Forgetting
-xfor sourced files. Without-x, ShellCheck only sees the top-level script and misses cross-file mistakes. - Not piping
-f difftopatch. Many quoting fixes are auto-applicable; doing them by hand is slower and error-prone. - Running shellcheck against shells it doesn't support. It does not check fish, zsh, or PowerShell. Use
fish_indent,zsh -n, andInvoke-ScriptAnalyzerrespectively.
Real-world recipes
Lint every script in the repo, fail on warnings
A useful Makefile target or one-liner for CI:
find . -path ./vendor -prune -o \( -name '*.sh' -o -name '*.bash' \) -print0 \
| xargs -0 shellcheck -x -S warning
Output:
(no output, exit 0 — everything clean)
Lint with a project allowlist of rules
When migrating a legacy codebase, freeze the current rule set and only enforce what you've already cleaned. Add rules back as you fix them.
# .shellcheckrc — strict subset only, expand over time
shell=bash
severity=warning
disable=SC2034,SC2155,SC1091 # not ready yet
enable=quote-safe-variables
Output: (none — exits 0 on success)
Annotate findings in a PR via gcc format
ShellCheck's gcc output format is parsed by most CI runners and turns each finding into a clickable line in the PR diff.
shellcheck -x -f gcc -S warning ./scripts/*.sh \
| tee shellcheck.log
# CI uploads shellcheck.log as a problem-matcher / SARIF artefact
Output:
scripts/deploy.sh:12:8: note: Double quote to prevent globbing and word splitting. [SC2086]
scripts/deploy.sh:20:1: warning: 'cd' may fail. Use 'cd ... || exit' or similar. [SC2164]
Auto-apply safe fixes across the repo
The -f diff format produces a real patch. For small, well-scoped changes (SC2006 backticks, SC2086 quoting) this saves hours of manual work.
# Generate one combined patch
find . -name '*.sh' -print0 \
| xargs -0 -I{} shellcheck -f diff {} >> fixes.patch
# Review what's about to change
diffstat fixes.patch
# Apply
patch -p1 < fixes.patch
Output:
deploy.sh | 4 ++--
setup.sh | 6 +++---
backup.sh | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
Lint heredoc / dotfiles / scripts without a .sh extension
ShellCheck infers the shell from the shebang, so a script called install works fine. For files without a shebang, force the dialect.
shellcheck -s bash install
shellcheck -s bash ~/.bashrc ~/.bash_profile
shellcheck -s sh /etc/init.d/myservice
Output:
(findings, if any)
Quiet check inside a Makefile target
lint:
@find . -name '*.sh' -print0 | xargs -0 shellcheck -x -S warning
@echo "shellcheck: clean"
.PHONY: lint
Output:
$ make lint
shellcheck: clean
Test the linter itself with a deliberately broken script
When wiring up CI, stash a known-bad script to confirm the pipeline actually fails when it should.
cat > /tmp/bad.sh <<'EOF'
#!/usr/bin/env bash
files=$(find . -name *.log)
for f in $files; do
rm $f
done
EOF
shellcheck /tmp/bad.sh
echo "exit=$?"
Output:
In /tmp/bad.sh line 2:
files=$(find . -name *.log)
^---^ SC2061 (warning): Quote the parameter to -name so the shell won't interpret it.
^----^ SC2207 (warning): Prefer mapfile or read -a to split command output ...
In /tmp/bad.sh line 3:
for f in $files; do
^----^ SC2086 (info): Double quote to prevent globbing and word splitting.
In /tmp/bad.sh line 4:
rm $f
^-- SC2086 (info): Double quote to prevent globbing and word splitting.
exit=1
Use ShellCheck as a library from CI
The json1 format is the cleanest input for any downstream tool — for example, posting findings as PR comments.
shellcheck -f json1 deploy.sh \
| jq -r '.comments[] | "\(.file):\(.line):\(.column) SC\(.code) (\(.level)) \(.message)"'
Output:
deploy.sh:12:8 SC2086 (info) Double quote to prevent globbing and word splitting.
deploy.sh:20:1 SC2164 (warning) Use 'cd ... || exit' or similar to handle cd failures.
The single best
shellcheckflag is-x(external-sources). Without it ShellCheck only sees the file you point at and misses cross-file bugs that are exactly the kind of mistake the tool was built to catch.
Combine
shellcheck -f diff … | patch -p1with a clean git working tree — you can preview every machine-suggested fix withgit diffand roll it back instantly withgit restoreif any change looks wrong.
Sources
- ShellCheck releases on GitHub — canonical changelog, including v0.11.0 (Aug 2025) and v0.10.0 (Mar 2024).
- ShellCheck wiki — Directive reference — every
.shellcheckrc/ inline directive, includingextended-analysisandexternal-sources. - ShellCheck wiki — Checks index — full SC#### code list with per-rule pages and examples.
- koalaman/shellcheck-precommit — official pre-commit hook repository, pinned by tag.