bash pipestatus 在反引号命令中?

Posted

技术标签:

【中文标题】bash pipestatus 在反引号命令中?【英文标题】:bash pipestatus in backticked command? 【发布时间】: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

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

谢谢。

【问题讨论】:

【参考方案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

【讨论】:

以上是关于bash pipestatus 在反引号命令中?的主要内容,如果未能解决你的问题,请参考以下文章

在反引号内传递变量[重复]

在go语言中,如何在反引号中调用变量的值而不是变量名

Newtonsoft.JSON 在反序列化数组中被双引号包围的对象时窒息

使用 AngularJS 的 typescript 时如何在反引号中获取 html/css snipits

Shell中的单引号(‘)双引号(”)和反引号(·)

bash中双引号单引号反撇号的用法