When you execute a pipeline, each pipe-separated element is executed in its own process. Variable assignments only take effect in their own process. Under ksh and zsh, the last element of the pipeline is executed in the original shell; under other shells such as bash, each pipeline element is executed in its own subshell and the original shell just waits for them all to end.
$ bash -c 'GROUPSTATUS=foo; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is foo
$ bash -c 'GROUPSTATUS=foo | :; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is
In your case, since you only care about all the commands succeeding, you can make the status code flow up.
{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2;
! ((PIPESTATUS[0])); } 3>&1 |
gzip --rsyncable > my_file.tar.gz;
if ((PIPESTATUS[0] || PIPESTATUS[1])); then rm my_file.tar.gz; fi
If you want to get more than 8 bits of information out of the left side of a pipe, you can write to yet another file descriptor. Here's a proof-of-principle example:
{ { tar …; echo $? >&4; } | …; } | { gzip …; echo gzip$? >&4; } \
4>&1 | ! grep -vxc '0'
Once you get data on standard output, you can feed it into a shell variable using command substitution, i.e. $(…). Command substitution reads from the command's standard output, so if you also meant to print things to script's standard output, they need to temporarily go through another file descriptor. The following snippet uses fd 3 for things that eventually go to the script's stdout and fd 4 for things that are captured into $statuses.
statuses=$({ { tar -v … >&3; echo tar $? >&4; } | …; } |
{ gzip …; echo gzip $? >&4; } 4>&1) 3>&1
If you need to capture the output from different commands into different variables, I think there is no direct way even in “advanced” shells such as bash, ksh or zsh. Here are some workarounds:
- Use temporary files.
- Use a single output stream, with e.g. a prefix on each line to indicate its origin, and filter at the top level.
- Use a more advanced language such as Perl or Python.