Lesson 15: Unquoted Line Continuation in Command Substitution¶
File affected: scripts/actions/clipboard.sh
Severity: High — select_and_paste always fails; fuzzel is never invoked
What Happened¶
select_and_paste() {
if command -v cliphist &>/dev/null; then
local selected
if command -v fuzzel &>/dev/null; then
selected=$(cliphist list | fuzzel --dmenu --theme="$HOME/.config/fuzzel/fuzzel.ini"
--prompt "Clipboard" --placeholder "Search clipboard history..."
--match-mode="fuzzy" --width 400 --height 300)
The command substitution $(...) spans three visual lines, but there are no
continuation backslashes (\). In bash, a newline inside $(...) terminates the
command — it does not automatically continue to the next line.
So the shell sees:
then a newline, which ends the fuzzel command. The remaining two lines: are parsed as a new command at the top level of the function body — bare words that are not a valid command. The whole function body fails to parse.With set -euo pipefail, the script exits. Without it, selected is empty and
the paste silently does nothing.
The Fix¶
Add backslash line continuations inside the command substitution:
selected=$(cliphist list | fuzzel --dmenu \
--theme="$HOME/.config/fuzzel/fuzzel.ini" \
--prompt "Clipboard" \
--placeholder "Search clipboard history..." \
--match-mode="fuzzy" \
--width 400 \
--height 300)
Or put it on one line:
selected=$(cliphist list | fuzzel --dmenu --theme="$HOME/.config/fuzzel/fuzzel.ini" --prompt "Clipboard" --width 400 --height 300)
The General Rule¶
Inside
$(...),$(( )),[[ ]], and(( )), newlines end the current command. They do NOT automatically continue the command on the next line. You must use\to continue, or keep the command on one line.
# BAD — newline terminates the command early
result=$(some-command --flag-one
--flag-two
--flag-three)
# GOOD — explicit continuation
result=$(some-command --flag-one \
--flag-two \
--flag-three)
# ALSO GOOD — single line
result=$(some-command --flag-one --flag-two --flag-three)
This also applies to pipelines:
# BAD — pipe on next line is a new command, not a continuation
result=$(grep "pattern" file
| sort
| head -5)
# GOOD — pipe at end of line continues
result=$(grep "pattern" file \
| sort \
| head -5)
# ALSO GOOD — pipe at start with backslash
result=$(grep "pattern" file |
sort |
head -5) # trailing pipe continues the expression — no backslash needed
How to Catch It¶
bash -n scripts/actions/clipboard.sh
# Syntax errors from unintended line breaks appear here
shellcheck scripts/actions/clipboard.sh
# SC1078: double-quote mismatch, SC1079: unexpected token
Also: any $( with a multi-line body that lacks \ at every non-final line is
a candidate for this bug.