What does SC2034 mean?
SC2034, "appears unused. Verify use (or export if used externally)", is ShellCheck's warning-level finding that a variable is assigned somewhere in the file and never read anywhere in it. The message is careful: verify use. ShellCheck sees one file, so a variable that is read by a sourced script or a child process looks dead to it. The rest of the time it is dead, and dead code in a shell script is usually a typo with a live twin.
What actually breaks?
Two things, both from this site.
The repo. On 2026-07-19 the README of this site's script repository said every script was ShellCheck-clean. It was not. shellcheck -S style across the 31 scripts that day found six with findings, and three of those failed on SC2034: bash-functions-arguments.sh and find-large-files-linux.sh defined CROSS="✗" beside CHECK="✓" and never printed it, and ssh-key-setup-script.sh carried KEY_BITS="4096" # Only used for RSA keys that nothing used, because the script generates ed25519 keys. Commit 093febd deleted the three lines. Dead code, and the SSH one lied: a reader would reasonably believe the RSA key size was configurable.
The typo. A log-pruning script with a one-letter mismatch, run on this box with bash 5.3.9 and GNU findutils 4.11.0:
$retention_day expanded to nothing, find received -mtime + and refused. That is the lucky outcome: the prune never runs and the disk fills slowly. Written as -mtime +${retention_day:-0}, a common defensive habit, find would have received -mtime +0 and deleted every log older than today. With set -u the script stops on line 5 with retention_day: unbound variable instead.
What does ShellCheck say, and what is the fix?
The pair is the tell. An SC2034 on one line and an SC2154 on the next with a did you mean pointing back at it is a typo, and the fix is to spell it the same way twice:
The repo's case had no twin. CROSS was defined and never referenced, so the fix was deletion:
Where else does SC2034 show up?
Single quotes. A variable whose only use is inside single quotes is not used, because single quotes do not expand it. HOST=$(hostname) followed by echo 'disk on $HOST is full' earns SC2034 on the assignment and SC2016 on the string, and the alert goes out with a literal $HOST in it.
Loop variables. for i in 1 2 3; do echo ping; done is flagged: i appears unused. The convention ShellCheck accepts is _, and for _ in 1 2 3 passes. The same works with read -r _ second when only the second field matters.
Library files. A lib.sh that defines CHECK and CROSS for the scripts that source it is flagged on every variable the library itself does not read. Two honest fixes. export CROSS="✗" passes, and is correct only if a child process reads it. A file-level directive with the reason is the other:
Config files. A file that is nothing but assignments has no shebang and no reads. # shellcheck shell=bash disable=SC2034 as its first line tells ShellCheck both what it is and why nothing in it is read.
When should I disable SC2034, and how?
When the variable is read by something ShellCheck cannot see and export would be a lie: a sourced library, a config file, a variable a trap handler reads by name. Use the file-level form for those files and the line form for one-offs:
For one run, shellcheck --exclude=SC2034 script.sh; for a project, disable=SC2034 in .shellcheckrc; to lint for this rule alone, shellcheck --include=SC2034 script.sh. Before reaching for any of them, search the file for the name once. Three times out of four the variable is dead and the fix is dd.
Related codes
- SC2154 — the use side of the same typo. See the SC2154 deep dive.
- SC2153 — the uppercase near-miss, at info level.
- SC2016 — the single-quoted string that made the variable "unused". See the SC2016 deep dive.
- SC1091 — a sourced file ShellCheck is not following;
-xfollows it.
Any other code: paste it into the ShellCheck Error Decoder.