4

I want to capture the exit status of a command that takes place somewhere in a pipeline before the last position. For example, if the pipeline is something like

command_1 ... | command_2 ... | command_3 ... | ... | command_n

...I would like to know how to capture the exit status of command_1, or of command_2, or of command_3, etc. (Capturing the exit status of command_n is trivial, of course.)

Also, in case it matters, this pipeline is occurring inside a zsh shell function.


I tried to capture the exit status of command_1 with something like

function_with_pipeline () {

    local command_1_status=-999999  # sentinel value

    { command_1 ...; command_1_status=$? } | command_2 ... | ... | command_n
    ...

}

...but after running the pipeline, the value of the command_1_status variable was still the sentinel value.


FWIW, here's a working example, where the pipeline has only two commands:

foo ... | grep ...

foo is a function defined for the sake of this example, like so:

foo () {

    (( $1 & 1 )) && echo "a non-neglible message"
    (( $1 & 2 )) && echo "a negligible message"
    (( $1 & 4 )) && echo "error message" >&2

    return $(( ( $1 & 4 ) >> 2 ))
}

The goal is to capture the exit status of the call to foo in the pipeline.

The function function_with_pipeline implements the (ultimately ineffective) strategy I described above to do this:

function_with_pipeline () {

    local foo_status=-999999  # sentinel value

    { foo $1; foo_status=$? } | grep -v "a negligible message"

    printf '%d\ndesired: %d; actual: %d\n\n' $1 $(( ( $1 & 4 ) >> 2 )) $foo_status

}

The loop below exercises the function_with_pipeline function. The output shows that the value of the local variable foo_status ends up no different from how it started.

for i in $(seq 0 7)
do
    function_with_pipeline $i
done
# 0
# desired: 0; actual: -999999
# 
# a non-neglible message
# 1
# desired: 0; actual: -999999
# 
# 2
# desired: 0; actual: -999999
# 
# a non-neglible message
# 3
# desired: 0; actual: -999999
# 
# error message
# 4
# desired: 1; actual: -999999
# 
# error message
# a non-neglible message
# 5
# desired: 1; actual: -999999
# 
# error message
# 6
# desired: 1; actual: -999999
# 
# error message
# a non-neglible message
# 7
# desired: 1; actual: -999999
#

I get the same results if I omit the local declaration in the definition of foo_status.

jimmij
  • 47,140
kjo
  • 15,339
  • 25
  • 73
  • 114

2 Answers2

5

There is special array pipestatus for that in zsh, so try

command_1 ... | command_2 ... | command_3

and

echo $pipestatus[1] $pipestatus[2] $pipestatus[3]

and the reason your approach doesn't work is because each pipe runs in separate subshell, with its own variables which are destroyed once you exit the subshell.


Just for reference, it is PIPESTATUS (with capital letters) in bash.

jimmij
  • 47,140
2

mispipe works in any shell. The syntax, (compared to a regular pipe), works like so:

mispipe true false ; echo $?  # returns exit code of 1st command `true`
true | false ; echo $?  # returns exit code of 2nd command `false`

Output:

0
1

What to do if there's more than two programs:

# this still returns exit code of 1st command `true`
mispipe true 'false | false | false' ; echo $?

Output:

0

Despite the lack of a visible |, it still behaves as a pipe should:

yes | mispipe head 'wc -c'

Output:

     20
agc
  • 7,223