SC2155 — Split declare and assign: fix in 30 seconds
Fix ShellCheck SC2155: local foo=$(cmd) always returns 0, so set -e never sees the failure. Split the declaration from the assignment instead.
Problem
When you combine `local`, `readonly`, `declare`, or `export` with a command substitution in one line, the exit code of the substitution is thrown away. The outer `local` or `export` always succeeds, so `set -e` will not catch a failing inner command and `$?` will report 0 even when `cmd` failed.
Bad
set -e
my_func() {
local result=$(failing_command) # always exits 0
echo "$result"
}Good
set -e
my_func() {
local result
result=$(failing_command) || return
echo "$result"
}Explanation
Splitting the declaration and assignment lets the assignment's own exit status (which equals the substitution's exit status) propagate. Now `set -e` aborts on failure and you can chain `|| return` or `|| handle_error` for explicit handling.
Related
SC2181
SC2086
When It Matters
declare, local, export, and readonly are commands, and their exit status is their own — not that of the command substitution inside the assignment. local out=$(failing_cmd) therefore succeeds even under set -e, and the script continues with an empty variable. In deployment scripts this converts a failed lookup into an empty path or an empty host name, which is then used as if it were valid.
Second Example
Note
Once the assignment is on its own line, set -e sees it and the || handler works as expected.
Exceptions
When the substitution cannot fail in any meaningful way — date, a pure parameter expansion, a literal — the combined form is harmless. It is still worth splitting for consistency, because the cost is one line and the failure mode is silent.
Faq
Q
Does this apply to plain assignments too?
A
No. var=$(cmd) on its own does propagate the command substitution status, so set -e catches it. Only declaration commands mask it.
Q
Which keywords are affected?
A
local, declare, typeset, export, and readonly — anything that is a command rather than a bare assignment.
Q
Can I keep one line and still check?
A
Not reliably. Split the declaration and the assignment; it is the only form that both declares scope and reports failure.