For new checks and feature suggestions
The closest existing issues are #1510 (command substitution in an if condition masking errexit) and #3242, which cover different situations. This one is about $? being silently replaced by a command substitution's own exit status within the same command.
Here's a snippet or screenshot that shows the problem:
#!/bin/bash
f=/etc/hosts
grep -q zzzznonexistentzzz "$f"
echo "direct : $?"
grep -q zzzznonexistentzzz "$f"
echo "with substitution : $(basename "$f") $?"
grep -q zzzznonexistentzzz "$f"
r=$?
echo "captured first : $(basename "$f") $r"
Output:
direct : 1
with substitution : hosts 0
captured first : hosts 1
Bash expands arguments left to right, so $(basename "$f") runs before $? is read and replaces $? with its own exit status. The reported status is basename's, never grep's.
Here's what shellcheck currently says:
Nothing. No output, exit status 0, on shellcheck 0.11.0.
Here's what I wanted or expected to see:
A warning on $? used in a command whose earlier expansions include a command substitution, suggesting the status be captured into a variable first.
Why this matters in practice
This bites hardest in test and verification helpers, where the label being built is exactly what triggers it:
check() { [ "$2" = 0 ] && echo "[ok] $1" || echo "[FAIL] $1"; }
run_the_thing
check "$(basename "$file") : produced valid output" $?
Every such call reports success unconditionally. The check cannot fail, so a red result is unreachable and the suite is green by construction.
I hit this in a hardware verification script: twelve checks written this way had been passing for hours while measuring nothing. They looked identical to the ones that worked.
SC2319 is adjacent but does not cover it. It fires on $? read after a condition ([ ... ]; r=$?), which is a different pattern, and it does not fire on any of the three lines above. Running shellcheck on the corrected form — where the status is captured first, which is the recommended fix — is what produces SC2319 warnings, while the broken form stays silent.
I tested four variants on 0.11.0:
| snippet |
shellcheck output |
echo "$(basename "$f")" $? |
nothing |
printf '%s %s' "$(basename "$f")" $? |
nothing |
r=$?; echo "$(basename "$f")" "$r" |
nothing |
if [ $? -eq 0 ] |
SC2181 |
A script can therefore be clean by shellcheck's standards and still contain checks that are structurally incapable of failing.
Note
The false-negative is the report. The SC2319 observation is context, not a claim that the rule is wrong — it targets a different pattern and does so correctly.
For new checks and feature suggestions
The closest existing issues are #1510 (command substitution in an
ifcondition maskingerrexit) and #3242, which cover different situations. This one is about$?being silently replaced by a command substitution's own exit status within the same command.Here's a snippet or screenshot that shows the problem:
Output:
Bash expands arguments left to right, so
$(basename "$f")runs before$?is read and replaces$?with its own exit status. The reported status isbasename's, nevergrep's.Here's what shellcheck currently says:
Nothing. No output, exit status 0, on
shellcheck 0.11.0.Here's what I wanted or expected to see:
A warning on
$?used in a command whose earlier expansions include a command substitution, suggesting the status be captured into a variable first.Why this matters in practice
This bites hardest in test and verification helpers, where the label being built is exactly what triggers it:
Every such call reports success unconditionally. The check cannot fail, so a red result is unreachable and the suite is green by construction.
I hit this in a hardware verification script: twelve checks written this way had been passing for hours while measuring nothing. They looked identical to the ones that worked.
SC2319is adjacent but does not cover it. It fires on$?read after a condition ([ ... ]; r=$?), which is a different pattern, and it does not fire on any of the three lines above. Runningshellcheckon the corrected form — where the status is captured first, which is the recommended fix — is what producesSC2319warnings, while the broken form stays silent.I tested four variants on 0.11.0:
echo "$(basename "$f")" $?printf '%s %s' "$(basename "$f")" $?r=$?; echo "$(basename "$f")" "$r"if [ $? -eq 0 ]A script can therefore be clean by shellcheck's standards and still contain checks that are structurally incapable of failing.
Note
The false-negative is the report. The
SC2319observation is context, not a claim that the rule is wrong — it targets a different pattern and does so correctly.