Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bash pipestatus in backticked command?

in bash, if I execute a couple of commands piped together inside of backticks, how can I find out the exit status of the first command?

i.e. in this case, I am trying to get the "1". which I can get via PIPESTATUS[0] if I am not using backticks, but which doesn't seem to work when I want to saving the output:

## PIPESTATUS[0] works to give me the exit status of 'false':
$ false | true;
$ echo $? ${PIPESTATUS[0]} ${PIPESTATUS[1]};
0 1 0

## doesn't work:
$ a=`false | true`;
$ echo $? ${PIPESTATUS[0]} ${PIPESTATUS[1]};
0 0

More generally, I am trying to accomplish: save the last line of the output of some program to a variable, but be able to tell if the program failed:

$ myvar=` ./someprogram | tail -1 `;
$ if [ "what do i put here" ]; then echo "program failed!"; fi

Ideally I'd also like to understand what is going on, not just what the answer is.

Thanks.

like image 854
jerry Avatar asked Jul 21 '13 19:07

jerry


2 Answers

Try to set pipefail option. It returns the last command of the pipeline that failed. One example:

First I disable it:

set +o pipefail

Create a perl script (script.pl) to test the pipeline:

#!/usr/bin/env perl

use warnings;
use strict;

if ( @ARGV ) { 
    die "Line1\nLine2\nLine3\n";
}
else {
    print "Line1\nLine2\nLine3\n";
}

Run in command-line:

myvar=`perl script.pl | tail -1`
echo $? "$myvar"

That yields:

0 Line3

It seems correct, let see with pipefail enabled:

set -o pipefail

And run the command:

myvar=`perl script.pl fail 2>&1 | tail -1`
echo $? "$myvar"

That yields:

255 Line3
like image 156
Birei Avatar answered Sep 28 '22 16:09

Birei


My solution was using fifos and the bash "coproc" builtin to get the messages and status from each command in the pipe. I'd never used fifos before. (oh boy, next time I'm using BashEclipse on Fedora). It turned into a generalized mechanism for managing any pipe command. I solved the problem, but not in 10 or 20 lines of code. more like 200 for a robust drop-in re-usable solution (took me three days to do so).

I share my notes:

* stderr for all pipe commands goes to the fifos.  
  If you want to get messages from stdout, you must redirect '1>&2', like this:  
  PIPE_ARRAY=("cat ${IMG}.md5" "cut -f1 -d\" \" 1>&2")  
  You must put "2>/fifo" first. Otherwise it won\'t work. example:  
    cat ${IMG}.md5 | cut -f1 -d' ' 1>&2  
  becomes:  
    cat ${IMG}.md5 2>/tmp/fifo_s0 | cut -f1 -d" " 2>/tmp/fifo_s1 1>&2 ; PSA=( "${PIPESTATUS[@]}" )

* With more tha one fifo, I found that you must read each fifo in turn.  
  When "fifo1" gets written to, "fifo0" reads are blocked until you read "fifo1"  
  I did\'nt use any special tricks like "sleep", "cat", or extra file descriptors  
  to keep the fifos open.  

* PIPESTATUS[@] must be copied to an array immediately after the pipe command returns.  
  _Any_ reads of PIPESTATUS[@] will erase the contents. Super volatile !  
  "manage_pipe()" appends '; PSA=( "${PIPESTATUS[@]}" )' to the pipe command string  
  for this reason. "$?" is the same as the last element of "${PIPESTATUS[@]}",  
  and reading it seems to destroy "${PIPESTATUS[@]}", but it's not absolutly verifed.

run_pipe_cmd() {  
  declare -a PIPE_ARRAY MSGS  
  PIPE_ARRAY=("dd if=${gDEVICE} bs=512 count=63" "md5sum -b >${gBASENAME}.md5")  
  manage_pipe PIPE_ARRAY[@] "MSGS"  # (pass MSGS name, not the array) 
}  
manage_pipe () {
  # input  - $1 pipe cmds array, $2 msg retvar name
  # output - fifo msg retvar
  # create fifos, fifo name array, build cnd string from $1 (re-order redirection if needed)
  # run coprocess 'coproc execute_pipe FIFO[@] "$CMDSTR"'
  # call 'read_fifos FIFO[@] "M" "S"' (pass names, not arrays for $2 and $3)
  # calc last_error, call _error, _errorf
  # set msg retvar values (eval ${2}[${i}]='"${Msg[${i}]}"')
}
read_fifos() {  
  # input  - $1 fifo array, $2 msg retvar name, $3 status retvar name  
  # output - msg, status retvars  
  # init local fifo_name, pipe_cmd_status, msg arrays  
  # do read loop until all 'quit' msgs are received
  # set msg, status retvar values (i.e. eval ${3}[${i}]='"${Status[${i}]}"' 
}
execute_pipe() {  
  # $1 fifo array, $2 cmdstr, $3 msg retvar, $4 status retvar   
  # init local fifo_name, pipe_cmd_status arrays
  # execute command string, get pipestaus  (eval "$_CMDSTR" 1>&2)
  # set fifo statuses from copy of PIPESTATUS
  # write 'status', 'quit' msgs to fifo  
}
like image 44
maxdev137 Avatar answered Sep 28 '22 17:09

maxdev137