bash pipestatus in backticked command?

后端 未结 3 656
一整个雨季
一整个雨季 2020-12-20 22:56

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 tryin

相关标签:
3条回答
  • 2020-12-20 23:35

    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  
    }
    
    0 讨论(0)
  • 2020-12-20 23:42

    The problem is that backticks launch a sub-shell. Your sub-shell has its own ${PIPESTATUS[@]} array, but that does not persist into the parent shell. Here's a trick to shove it into the output variable $a and then retrieve it into a new array called ${PIPESTATUS2[@]}:

    ## PIPESTATUS[0] works to give me the exit status of 'false':
    $ false | true
    $ echo $? ${PIPESTATUS[0]} ${PIPESTATUS[1]}
    0 1 0
    
    ## Populate a $PIPESTATUS2 array:
    $ a=`false | true; printf :%s "${PIPESTATUS[*]}"`
    $ ANS=$?; PIPESTATUS2=(${a##*:})
    $ [ -n "${a%:*}" ] && a="${a%:*}" && a="${a%$'\n'}" || a=""
    $ echo $ANS ${PIPESTATUS2[0]} ${PIPESTATUS2[1]};
    0 1 0
    

    This saves the sub-shell's ${PIPESTATUS[@]} array as a space-delimited list of values at the end of $a and then extracts it using shell variable substring removal (see the longer example and description I gave to this similar question). The third line is only needed if you actually want to save the value of $a without the extra statuses (as if it were run as false | true in this example).

    0 讨论(0)
  • 2020-12-20 23:50

    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
    
    0 讨论(0)
提交回复
热议问题