【问题标题】:bash pipestatus in backticked command?bash pipestatus 在反引号命令中?
【发布时间】:2013-07-20 12:15:18
【问题描述】:

在 bash 中,如果我在反引号内同时执行几个命令,如何找出第一个命令的退出状态?

即在这种情况下,我试图得到“1”。如果我不使用反引号,我可以通过 PIPESTATUS[0] 获得它,但是当我想保存输出时它似乎不起作用:

## 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

更一般地说,我正在尝试完成:将某个程序输出的最后一行保存到变量中,但能够判断程序是否失败:

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

理想情况下,我还想了解正在发生的事情,而不仅仅是答案。

谢谢。

【问题讨论】:

    标签: bash pipe backticks


    【解决方案1】:

    问题是反引号会启动一个子shell。您的子 shell 有自己的 ${PIPESTATUS[@]} 数组,但它不会保留在父 shell 中。这是将其推入输出变量$a 然后将其检索到名为${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
    

    这会将子shell的${PIPESTATUS[@]}数组保存为$a末尾的空格分隔的值列表,然后使用shell变量substring removal提取它(请参阅我给@987654322的更长示例和描述@)。仅当您确实想要保存 $a 的值而不需要额外的状态时才需要第三行(就像在此示例中以 false | true 运行一样)。

    【讨论】:

      【解决方案2】:

      我的解决方案是使用 fifos 和内置的 bash “coproc” 从管道中的每个命令中获取消息和状态。我以前从未使用过fifos。 (哦,男孩,下次我在 Fedora 上使用 BashEclipse 时)。它变成了管理任何管道命令的通用机制。我解决了这个问题,但不是 10 或 20 行代码。更像是 200 个 强大的插入式可重复使用解决方案(我花了三天时间才完成)。

      我分享我的笔记:

      * 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  
      }
      

      【讨论】:

        【解决方案3】:

        尝试设置pipefail 选项。它返回失败的管道的最后一个命令。一个例子:

        首先我禁用它:

        set +o pipefail
        

        创建一个perl 脚本 (script.pl) 来测试管道:

        #!/usr/bin/env perl
        
        use warnings;
        use strict;
        
        if ( @ARGV ) { 
            die "Line1\nLine2\nLine3\n";
        }
        else {
            print "Line1\nLine2\nLine3\n";
        }
        

        在命令行中运行:

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

        产生:

        0 Line3
        

        这似乎是正确的,让我们看看启用pipefail

        set -o pipefail
        

        然后运行命令:

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

        产生:

        255 Line3
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-28
          • 2021-10-23
          • 2021-07-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多