What does SC2046 mean?
SC2046, "Quote this to prevent word splitting", is ShellCheck's warning-level finding that the output of a $(command) or backtick substitution is used unquoted. Bash splits that text on spaces, tabs and newlines and then expands any * or ? in the pieces, so the outer command receives however many arguments the output happened to contain. Like SC2086 but for command output, and the fix is usually not quotes.
What actually breaks?
A directory with two private keys, one of them inside a directory with a space in its name, plus two stray files that happen to match the split pieces. Every command below was run on this box with bash 5.3.9 and GNU findutils 4.11.0.
find printed two paths on two lines. Bash split them into three words: ./old, backups/server.key and ./server.key. The stray file old is now mode 600, the path backups/server.key does not exist so chmod errored, and the key that lives in old backups/ is still world-readable. The script exited 1 only because the middle piece happened not to exist. Had old been a directory, chmod would have changed it and moved on.
What does ShellCheck say, and what is the fix?
No "Did you mean" line this time, because quoting is not the fix for a list. Let find run the command itself:
Both keys locked, the stray files untouched. -exec ... {} + passes the paths to chmod as separate arguments without ever turning them into text, so spaces and newlines in names cannot split anything. When you need the list in the script rather than in one command, read it into an array with NUL separators:
This site's own script repo got the same lesson in July from the neighbouring code SC2038: find "$TARGET" -type f | xargs -r md5sum in the duplicate-file scanner became find "$TARGET" -type f -print0 | xargs -0 -r md5sum. A filename with a space in a duplicate scan is a wrong hash on a path that does not exist.
Where else does SC2046 show up?
Parsing ls. rm $(ls *.tmp) earns SC2046 and SC2035 in one line. ls output is for humans; the glob itself is the list: rm -- *.tmp.
cd $(dirname $0). Three findings on one line: SC2164 because cd can fail, SC2046 for the substitution, SC2086 for $0. The clean version is cd "$(dirname "$0")" || exit 1. Here the substitution is a single value, so quoting is the right fix.
Inside [ ]. [ $(wc -l < /etc/passwd) -gt 10 ] is SC2046 too. wc -l on a redirect prints one number, so "$(wc -l < /etc/passwd)" is correct and ShellCheck passes it.
for f in $(find ...). Not SC2046. ShellCheck reports SC2044, "For loops over find output are fragile", and the fix is find -exec or a while IFS= read -r -d '' f loop over find -print0.
When should I disable SC2046, and how?
The honest case is a substitution whose output is a set of separate flags by design: $(pkg-config --cflags --libs foo), or a tool that prints options one per word. Even then mapfile -t flags < <(pkg-config --cflags --libs foo) followed by "${flags[@]}" is clean without a directive. If you keep the split, say why:
For one run, shellcheck --exclude=SC2046 script.sh. For a project, disable=SC2046 in .shellcheckrc. Because this is a warning, shellcheck -S error also hides it, along with every other warning, which is rarely what you want. To lint for this rule and nothing else: shellcheck --include=SC2046 script.sh.
Related codes
- SC2086 — the same split on an unquoted variable. See the SC2086 deep dive.
- SC2044 —
foroverfindoutput. - SC2038 —
find | xargswithout-print0and-0. See Find Duplicate Files. - SC2035, SC2012 — globs and
lsoutput as arguments.
Any other code: paste it into the ShellCheck Error Decoder.