What does SC2016 mean?
SC2016, "Expressions don't expand in single quotes, use double quotes for that", is ShellCheck's info-level note that a single-quoted string contains a $name, ${name} or backtick command. Single quotes are literal; whatever is between them is passed through untouched. Most of the time that is a mistake, and ShellCheck says so. Some of the time it is the point, because the string is going to another program that will do its own expanding.
What actually breaks?
A disk alert, run on this box with bash 5.3.9:
That is the line that lands in the on-call inbox. It says a disk is filling and names neither the host nor the number. Nothing failed, so set -euo pipefail had nothing to do, and the script exited 0.
What does ShellCheck say, and what is the fix?
Three findings for one bug, and the two SC2034 warnings are the useful ones: they say the variables are never read, which is what an unexpanded $HOST amounts to. Note what is not flagged: the $5 inside the awk program on line 4. ShellCheck knows that one belongs to awk.
Double quotes on line 5 clear all three:
Which commands does ShellCheck exempt?
Twelve lines through ShellCheck 0.11.0, each with a $ inside single quotes:
| Line | Verdict |
|---|---|
ssh backup-host 'du -sh $HOME/backups' | passes |
ssh backup-host 'rm -rf $TMPDIR/build' | passes |
bash -c 'echo $HOME' | passes |
find . -name '*.log' -exec sh -c 'echo $1' _ {} \; | passes |
awk '{print $1}' /etc/hostname | passes |
perl -ne 'print $_' /etc/hostname | passes |
xargs -I{} sh -c 'echo $1' _ {} | SC2016 |
mail -s 'Disk at $USAGE%' ops@example.com | SC2016 |
echo 'Cost: $5' | SC2016 |
sed 's/^/$PREFIX: /' /etc/hostname | SC2016 |
git config alias.who '!echo $1' | SC2016 |
echo 'expands: `date`' | SC2016 |
The exemptions are a model of where the string is going: ssh and bash -c are shells that will expand it, awk and perl own their dollar signs. The model has edges. find -exec sh -c '...' passes and the equivalent xargs sh -c '...' is flagged, because ShellCheck understands find -exec as a shell invocation and does not understand xargs. Treat the list as a convenience and read each finding on its merits: the sed and mail -s lines are real bugs, and the xargs and git config lines are correct as written.
When should I disable SC2016, and how?
When the string is genuinely meant to reach another program with its dollar signs intact and ShellCheck's exemption list did not cover it. Two ways to say so, and the second needs no directive:
The escaped form is often the better one. A reader sees \$ and knows the literal dollar is deliberate; a reader of the single-quoted form has to trust the comment above it.
For one run, shellcheck --exclude=SC2016 script.sh; for a project, disable=SC2016 in .shellcheckrc; shellcheck -S warning hides it with every other info-level note; to lint for this rule alone, shellcheck --include=SC2016 script.sh.
Related codes
- SC2034 — the variables the single quotes made "unused". See the SC2034 deep dive.
- SC2029 — the mirror image:
ssh host "cmd $VAR"expands on the client, not the server. - SC2087 — the same question for an
sshheredoc: quote theEOFto expand remotely. - SC2086 — the unquoted variable, the other quoting bug. See the SC2086 deep dive.
Any other code: paste it into the ShellCheck Error Decoder.