I started writing a response early in this thread and then set it aside. My initial impression was that excluding most compound commands from being reported in PIPESTATUS was at best questionable, but now I'm thinking otherwise.
It is obviously useful to be able to inspect PIPESTATUS after compound commands such as: - ! PIPELINE - case $thing in pattern1) PIPELINE1 ;; pattern2) PIPELINE2 ;; *) PIPELINE3 ; esac - if condition ; then PIPELINE1 ; else PIPELINE2 ; fi - while condition ; do PIPELINE ; done What seems more like a bug are cases like: - if FAIL ; then PIPELINE ; fi - while FAIL ; do PIPELINE ; done where PIPESTATUS reflects whatever FAIL did, and even worse cases like: - case NOMATCH in ... esac - select NAME in CHOICES ; do ... ; done </dev/null where PIPESTATUS is completely unaffected by the block, instead retaining its previous values. Much as I hate breaking changes, it is hard to see how these latter 3 could be used reliably in valid code, and they invite some rather insidious bugs, so I would like to propose a change: *That after finishing a compound command, **PIPESTATUS and $? are (set in ways that makes them appear to be) unaffected by **any command within any controlling condition LIST (before “do” or “then”), and if no (other) pipeline was executed inside the compound command**, $? is set to 0, and** PIPESTATUS is set to an empty list.* (This would not actually change the current behaviour of $?, but documenting PIPESTATUS and $? together like this would make it clearer when and how they differ.) The idea is that having PIPESTATUS be empty would indicate that *nothing* was executed; this would apply: - when the controlling condition is false in an “if/fi” block without an “else” clause; - when a loop has zero iterations (not counting initially evaluating the controlling condition LIST); - when a case/esac block has a WORD that does not match any of its PATTERNs; - (new syntax, for consideration) when ‘!’ is bare, with nothing between it and the next command terminator Then ${#PIPESTATUS[@]}==0 could conceivably be useful as a post-facto indication of zero iterations. Rationale: whatever is done, even leaving it unchanged, there is going to be *some* surprise; I think this proposal at least minimizes the surprise, since it means that the relationship between $? and PIPESTATUS is a bit less arcane. If there are compatibility concerns, it would be acceptable for this to be gated on a shopt such as “empty_pipestatus” or “not old_pipestatus” or perhaps “not compat53 or earlier” (or equivalently, “compat54 or later”). -Martin PS: before I considered setting PIPESTATUS to empty, I did wonder about simply making all compound commands transparent to PIPESTATUS (with the same exclusion of controlling conditions), but it quickly became evident that this would not yield a “better” result -- and would be much more complicated to implement. PPS: some examples: $ *while ( exit 42 ) ; do exit 43 | ( exit 44 ) ; done* ; declare -p PIPESTATUS declare -a PIPESTATUS='([0]="42")' $ *if ( exit 42 ) ; then exit 43 | ( exit 44 ) ; fi* ; declare -p PIPESTATUS declare -a PIPESTATUS='([0]="42")' $ ( exit 42 ) ; *case NOMATCH in esac* ; declare -p PIPESTATUS declare -a PIPESTATUS='([0]="42")' $ ( exit 42 ) ; *select FOO in BAR ; do echo $FOO ; ( exit 43 ) ; done </dev/null* ; declare -p PIPESTATUS 1) BAR #? declare -a PIPESTATUS='([0]="42")' $ set -- ; ( exit 42 ) ; *for X do exit 43 | ( exit 44 ) ; done* ; declare -p PIPESTATUS declare -a PIPESTATUS='([0]="42")'