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.