获取通过管道传递到另一个进程的退出状态


Answers:


255

如果使用bash,则可以使用PIPESTATUSarray变量获取管道中每个元素的退出状态。

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

如果使用zsh,则会调用它们的数组pipestatus(大小写很重要!),数组索引从1开始:

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

以不丢失值的方式将它们组合到函数中:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

bash或中运行以上操作,zsh您将获得相同的结果;只会设置retval_bash和之一retval_zsh。另一个将为空白。这将使函数以return $retval_bash $retval_zsh(以引号引起!)结尾。


9
pipestatus在zsh中。不幸的是,其他外壳程序没有此功能。
Gilles

8
注意:zsh中的数组从索引1开始反直觉,因此为echo "$pipestatus[1]" "$pipestatus[2]"
Christoph Wurm

5
您可以像这样检查整个管道:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0 2012年

7
@JanHudec:也许您应该阅读我答案的前五个字。还请指出问题在哪里要求仅POSIX答案。
camh 2014年

12
@ JanHudec:它也没有标记POSIX。您为什么认为答案必须是POSIX?没有指定,所以我提供了一个合格的答案。我的答案没有什么不妥,另外还有多个答案可以解决其他情况。
camh 2014年

236

有3种常见的方法:

管道失败

第一种方式是设置pipefail选项(kshzshbash)。这是最简单的操作,基本上是将退出状态设置为$?最后一个程序的退出代码,以退出非零值(如果成功退出,则返回零)。

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

Bash还具有一个名为$PIPESTATUS$pipestatusin zsh)的数组变量,其中包含最后一个管道中所有程序的退出状态。

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

您可以使用第3个命令示例来获取所需管道中的特定值。

单独执行

这是解决方案中最笨拙的。分别运行每个命令并捕获状态

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1

2
该死!我只是要发布关于PIPESTATUS的信息。
slm

1
为了参考也有在该SO问题讨论了几种其它技术:stackoverflow.com/questions/1221833/...
SLM

1
@Patrick pipestatus解决方案可在bash上使用,如果我使用ksh脚本,您可以采取更多措施,您认为我们可以找到类似于pipestatus的东西?,(与此同时,我看到ksh不支持
pipestatus

2
@yael我不使用ksh,但是从它的联机帮助页面看一眼,它不支持$PIPESTATUS或类似功能。它确实支持该pipefail选项。
Patrick

1
我决定使用pipefail,因为它允许我在此处获取失败命令的状态:LOG=$(failed_command | successful_command)
vmrob 2014年

51

此解决方案无需使用bash特定功能或临时文件即可工作。奖励:最后,退出状态实际上是退出状态,而不是文件中的某些字符串。

情况:

someprog | filter

您需要退出状态someprog和的输出filter

这是我的解决方案:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

此构造的结果从stdout filter作为构造的stdout ,退出状态从someprog作为构造的退出状态。


此构造也可用于简单的命令分组{...}而不是子shell (...)。子外壳会带来一些影响,其中包括性能成本,在这里我们不需要。阅读精美的bash手册以获取更多详细信息:https : //www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

不幸的是,bash语法要求花括号使用空格和分号,以使构造变得更加宽敞。

对于本文的其余部分,我将使用subshel​​l变体。


示例someprogfilter

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

输出示例:

filtered line1
filtered line2
filtered line3
42

注意:子进程从父进程继承打开文件描述符。这意味着someprog将继承打开的文件描述符3和4。如果someprog写入文件描述符3,则它将变为退出状态。实际的退出状态将被忽略,因为read只能读取一次。

如果您担心自己someprog可能会写入文件描述符3或4,那么最好在调用之前关闭文件描述符someprog

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

exec 3>&- 4>&-之前someprog执行前关闭文件描述符someprog那么someprog这些文件描述符根本不存在。

也可以这样写: someprog 3>&- 4>&-


逐步解释构造:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

从下至上:

  1. 创建一个子shell,并将文件描述符4重定向到stdout。这意味着在子shell中打印到文件描述符4的所有内容将最终成为整个构造的标准输出。
  2. 创建管道并执行左侧(#part3)和右侧(#part2)的命令。exit $xs也是管道的最后一个命令,这意味着stdin中的字符串将是整个构造的退出状态。
  3. 创建一个子shell,并将文件描述符3重定向到stdout。这意味着在此子shell中打印到文件描述符3的所有内容都将最终出现#part2,进而将成为整个构造的退出状态。
  4. 创建管道并执行左侧(#part5#part6)和右侧(filter >&4)的命令。的输出filter重定向到文件描述符4。在#part1文件描述符4中重定向到stdout。这意味着的输出filter是整个构造的标准输出。
  5. 退出状态从#part6打印到文件描述符3。在#part3文件描述符3中被重定向到#part2。这意味着from的退出状态#part6将是整个构造的最终退出状态。
  6. someprog被执行。退出状态为#part5。标准输出由管道#part4引入并转发到filter。的输出filter将依次达到stdout,如#part4

1
真好 对于该功能,我可能会做(read; exit $REPLY)
jthill '16

5
(exec 3>&- 4>&-; someprog)简化为someprog 3>&- 4>&-
罗杰·佩特

此方法也可以在没有子{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
外壳的

36

虽然不完全是您的要求,但您可以使用

#!/bin/bash -o pipefail

以便您的管道返回最后的非零收益。

可能少一些编码

编辑:示例

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1

9
set -o pipefail脚本中的脚本应该更健壮,例如,如果有人通过执行脚本bash foo.sh
maxschlepzig 2011年

这是如何运作的?你有例子吗?
约翰

2
请注意,这-o pipefail不在POSIX中。
scy 2013年

2
这在我的BASH 3.2.25(1)-发行版中不起作用。在/ tmp / ff的顶部,我有#!/bin/bash -o pipefail。错误是:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez

2
@FelipeAlvarez:某些环境(包括Linux)不执行分析上的空间#!以外的第一个线,所以这成为/bin/bash -o pipefail /tmp/ff,而不是必要的/bin/bash -o pipefail /tmp/ff- getopt(或类似)的解析使用optarg,这是在下一项目ARGV,作为参数到-o,因此失败。如果您要做一个包装器(例如,bash-pf这样做exec /bin/bash -o pipefail "$@"#!行了,然后把它放到网上,那将起作用。另请参见:en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes

22

如果可能的话,我要做的是将退出代码从中foo输入bar。例如,如果我知道foo永远不会产生仅包含数字的行,那么我可以添加退出代码:

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

或者如果我知道输出foo从不包含带有just的行.

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

如果除了bar最后一行之外还有其他方法可以工作,并且将最后一行作为退出代码传递,则总是可以这样做。

如果bar是不需要输出的复杂管道,则可以通过在其他文件描述符上打印退出代码来绕过它的一部分。

exit_codes=$({ { foo; echo foo:"$?" >&3; } |
               { bar >/dev/null; echo bar:"$?" >&3; }
             } 3>&1)

在此之后$exit_codes通常是foo:X bar:Y,但它可能是bar:Y foo:X,如果bar阅读所有输入之前,或者如果你运气不好退出。我认为在所有unices上写入最多512个字节的管道都是原子的,因此只要标签字符串在507个字节以下,就不会将foo:$?bar:$?部分混合在一起。

如果您需要从中捕获输出bar,则将变得很困难。您可以通过安排输出bar从不包含看起来像退出代码指示的行,但确实很麻烦,从而结合上述技术。

output=$(echo;
         { { foo; echo foo:"$?" >&3; } |
           { bar | sed 's/^/^/'; echo bar:"$?" >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

而且,当然,有使用临时文件存储状态的简单选项。很简单,但生产起来却不那么简单:

  • 如果有多个脚本同时运行,或者同一脚本在多个位置使用此方法,则需要确保它们使用不同的临时文件名。
  • 很难在共享目录中安全地创建临时文件。通常,这/tmp是脚本肯定能够写入文件的唯一位置。使用mktemp,这不是POSIX,但在当今的所有严重的Uniice上都可用。
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
当使用临时文件方法时,我更喜欢为退出添加一个陷阱,该陷阱将删除所有临时文件,以便即使脚本死了也不会留下垃圾
Miracle173

17

从管道开始:

foo | bar | baz

这是仅使用POSIX shell而没有临时文件的常规解决方案:

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$error_statuses 包含任何失败进程的状态码(以随机顺序),并带有索引以指示哪个命令发出了每个状态。

# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null

注意$error_statuses测试中的引号;没有它们grep就无法区分,因为换行符被强制为空格。


12

所以我想提供一个像莱斯曼纳的答案,但是我认为我的也许是一个更简单,更有利的纯伯恩壳解决方案:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

我认为这是最好的从内而外的解释-command1将执行并在stdout(文件描述符1)上打印其常规输出,然后完成后,printf将执行并在其stdout上打印command1的退出代码,但是该stdout重定向到文件描述符3。

当command1运行时,其stdout将通过管道传递给command2(printf的输出从不将其传递给command2,因为我们将其发送到文件描述符3而不是管道读取的1)。然后,我们将command2的输出重定向到文件描述符4,以便它也不会出现在文件描述符1中–因为我们希望稍后释放文件描述符1,因为我们会将文件描述符3的printf输出放回到文件描述符中1 –因为这是命令替换(反引号)将捕获的内容,因此将其放入变量中。

魔术的最后一点是,首先exec 4>&1我们作为一个单独的命令完成了–它打开文件描述符4作为外部外壳的stdout的副本。从命令内部的角度来看,命令替换将捕获标准上写的所有内容–但是,由于command2的输出就命令替换而言将到达文件描述符4,因此命令替换不会捕获它–但是,一旦“退出”命令替换,它实际上仍将转到脚本的整体文件描述符1。

(该exec 4>&1命令必须是一个单独的命令,因为当您尝试在命令替换内写入文件描述符时,许多常见的shell都不喜欢它,该命令在使用替换的“外部”命令中打开。因此,这是最简单的便携式方法。)

您可以用一种不太技术性且更有趣的方式来查看它,就像命令的输出彼此跳跃一样:command1通过管道传递到command2,然后printf的输出会跳过命令2,以便command2不会捕获它,然后命令2的输出跳出命令替换,就像printf恰好及时地被替换捕获一样,以便它最终出现在变量中,而命令2的输出以一种很快乐的方式写入标准输出,就像在普通管道中。

而且,据我所知,$?它将仍然在管道中包含第二个命令的返回代码,因为变量分配,命令替换和复合命令对于它们内部的命令的返回代码都是有效的透明的,因此返回状态为command2应该被传播出去-这并且不必定义其他功能,这就是为什么我认为这可能比lesmana提出的解决方案更好。

lesmana指出,在某种程度上,command1最终可能会使用文件描述符3或4,因此,为了更加健壮,您可以这样做:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

请注意,我在示例中使用了复合命令,但是使用了子shell(使用( )代替{ }也会起作用,尽管可能效率较低。)

命令从启动它们的进程中继承文件描述符,因此整行第二行将继承文件描述符4,随后的复合命令3>&1将继承文件描述符3。因此,请4>&-确保内部复合命令不会继承文件描述符四个,并且3>&-不会继承文件描述符三个,以便command1获得一个“更干净”的更标准的环境。您也可以将内部移动到4>&-旁边3>&-,但我认为为什么不尽可能限制其范围。

我不确定事情多久直接使用文件描述符3和4 –我认为大多数时候程序使用syscall来返回当前未使用的文件描述符,但是有时代码会直接写入文件描述符3猜测(我可以想象一个程序检查文件描述符以查看它是否打开,如果打开则使用它,或者如果没有打开则相应地表现不同)。因此,可能最好记住后者,并在通用情况下使用。


看起来很有趣,但是我不太清楚您希望该命令做什么,而我的计算机也不能。我懂了-bash: 3: Bad file descriptor
G-Man

@ G-Man是的,我一直忘记bash并不了解文件描述符,这与我通常使用的shell(busybox附带的ash)不同。当我想到使bash满意的解决方法时,我会通知您。同时,如果您有方便使用的debian盒子,则可以使用破折号尝试,如果您有好用的busybox,则可以使用busybox ash / sh进行尝试。
mtraceur 2015年

@ G-Man关于我期望命令执行的操作,以及它在其他shell中的操作,是从command1重定向stdout,这样它就不会被命令替换捕获,但是一旦在命令替换之外,它就会掉线fd3返回到stdout,以便按预期方式将其通过管道传递到command2。当command1退出时,printf触发并打印其退出状态,该退出状态通过命令替换捕获到变量中。此处非常详细的细分:stackoverflow.com/questions/985876/tee-and-exit-status/…另外,您的评论似乎是在侮辱您?
mtraceur

我应该从哪里开始?(1)对不起,如果您感到侮辱。“看起来很有趣”的意思是认真的。如果能够像您期望的那样紧凑地工作,那就太好了。除此之外,我只是在说,我不明白您的解决方案应该做什么。我已经在Unix上工作了长时间(因为Linux诞生之前),并且,如果我不了解某些内容,那是一个危险信号,也许其他人也不会理解它,并且它需要更多说明(IMNSHO)。…(续)
G-Man

(续)……由于 “……想…………[您]比普通人更了解一切”,也许您应该记住,Stack Exchange的目标不是成为命令编写服务,因此提供数千种一次性解决方案,以解决琐碎的问题;而是教人们如何解决自己的问题。并且,为此,也许您需要充分解释一些内容,以使“普通人”能够理解。看看lesmana的答案来举例说明。…(续)
G-Man


7

lesmana的上述解决方案也可以实现,而无需使用{ .. }代替启动嵌套子进程的开销(请记住,这种形式的分组命令总是必须以分号结尾)。像这样:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

我已经用破折号版本0.5.5和bash版本3.2.25和4.2.42检查了此结构,因此,即使某些shell不支持{ .. }分组,它仍然兼容POSIX。


这在我尝试过的大多数shell上都非常有效,包括NetBSD sh,pdksh,mksh,破折号,bash。但是,即使使用set -o pipefailksh或任意数量的分散命令,我也无法使其与AT&T Ksh(93s +,93u +)或zsh(4.3.9,5.2)一起wait使用。我认为,至少在某种程度上,这可能是ksh的解析问题,好像我坚持使用subshel​​l一样,它仍然可以正常工作,但是即使if选择了ksh的subshel​​l变体,但将复合命令留给其他人使用,它也会失败。
Greg A. Woods

4

这是可移植的,即可以与任何POSIX兼容的外壳一起使用,不需要当前目录可写,并且允许使用同一技巧的多个脚本同时运行。

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

编辑:这是继吉尔斯的评论之后的更强版本:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2:以下是在dubiousjim评论之后的一个稍微轻一些的变体:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))

3
由于某些原因,这不起作用。1.临时文件可以在写入之前先读取。2.在共享目录中以可预测的名称创建临时文件是不安全的(琐碎的DoS,符号链接争用)。3.如果同一脚本多次使用此技巧,则它将始终使用相同的文件名。解决方法1,在管道完成后读取文件。要解决2和3,请使用具有随机生成名称的临时文件或位于私有目录中。
Gilles

+1好$ {PIPESTATUS [0]}比较容易,但是只要知道Gilles提到的问题,这里的基本思想就可以奏效。
约翰

您可以保存一些子外壳:(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))。@Johan:我同意使用Bash会更容易,但是在某些情况下,知道如何避免Bash是值得的。
dubiousjim

4

如果您无法使用一种常见的解决方案,则以下内容是@Patrik答案的附加内容。

该答案假定如下:

  • 您有一个不知道$PIPESTATUS也不知道的外壳set -o pipefail
  • 您想使用管道进行并行执行,因此没有临时文件。
  • 如果您中断脚本(可能是突然断电),则不希望出现其他混乱情况。
  • 该解决方案应该相对易于遵循并且易于阅读。
  • 您不想引入其他子外壳。
  • 您无法摆弄现有的文件描述符,因此切勿触摸stdin / out / err(但是您可以暂时引入一些新的文件描述符)

其他假设。您可以摆脱所有的麻烦,但是这会使食谱变得过多,因此这里不介绍它:

  • 您只想知道PIPE中的所有命令的退出代码均为0。
  • 您不需要其他边带信息。
  • 您的Shell确实等待所有管道命令返回。

之前:foo | bar | baz,但这仅返回最后一个命令(baz)的退出代码

想要的:如果管道中的任何命令失败,则$?不得为0(true)

后:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

# $? now is 0 only if all commands had exit code 0

解释:

  • 使用创建临时文件mktemp。通常这会立即在/tmp
  • 然后将该临时文件重定向到FD 9进行写入,并重定向到FD 8进行读取
  • 然后,临时文件将立即删除。但是,它保持打开状态,直到两个FD都不存在为止。
  • 现在开始管道。如果有错误,则每个步骤仅添加到FD 9。
  • wait是需要ksh的,因为ksh别人不会等待所有管道命令来完成。但是请注意,如果存在某些后台任务,则会有不希望的副作用,因此默认情况下我将其注释掉。如果等待没有影响,可以对其进行注释。
  • 之后,读取文件的内容。如果为空(因为所有工作),则read返回false,因此true表示错误

可以将其用作单个命令的插件替换,仅需要满足以下条件:

  • 未使用的FD 9和8
  • 单个环境变量保存临时文件的名称
  • 而且此配方可以适应几乎所有允许IO重定向的外壳
  • 而且它与平台无关,不需要诸如此类的东西 /proc/fd/N

错误:

如果/tmp空间不足,此脚本有一个错误。如果您也需要保护以防这种人为的情况,则可以按以下步骤进行操作,但是这样做的缺点是,0in 000的数量取决于管道中的命令数量,因此稍微复杂一些:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

可移植性注意事项:

  • ksh而仅等待最后一个管道命令的类似shell需要未wait注释

  • 最后一个示例使用printf "%1s" "$?"代替,echo -n "$?"因为它更易于移植。并非每个平台都能-n正确解释。

  • printf "$?"也可以做到这一点,但是printf "%1s"如果您在某些真正损坏的平台上运行脚本,则会遇到一些极端情况。(阅读:如果您碰巧在中编程paranoia_mode=extreme。)

  • 在支持多位数的平台上,FD 8和FD 9可能更高。符合POSIX要求的外壳程序仅需要支持一位数字。

  • 与Debian的8.2测试shbashkshashsash甚至csh


3

采取一些预防措施,这应该起作用:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)

像jlliagre一样如何清理?您是否不将名为foo-status的文件留下?
约翰

@Johan:如果您喜欢我的建议,请毫不犹豫地投票;)除了不留下文件之外,它还具有允许多个进程同时运行此文件的优点,并且当前目录不需要可写。
jlliagre 2011年

2

以下“ if”块仅在“ command”成功执行后才会运行:

if command; then
   # ...
fi

具体来说,您可以运行以下内容:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

它将运行haconf -makerw并将其stdout和stderr存储到“ $ haconf_out”。如果from的返回值为haconftrue,则将执行'if'块并将其grep读取为“ $ haconf_out”,以使其与“ Cluster已经可写”相匹配。

注意管道会自动清理。使用重定向时,您必须小心删除“ $ haconf_out”。

不如优雅pipefail,但如果无法实现此功能,则是一个合理的选择。


1
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$

0

(至少使用bash)结合使用set -e一个可以使用子shell显式模拟pipefail并在出现管道错误时退出

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

因此,如果foo由于某种原因失败-程序的其余部分将不会执行,并且脚本会退出并显示相应的错误代码。(这假定foo打印出自己的错误,足以理解失败的原因)


-1

编辑:这个答案是错误的,但是很有趣,所以我将其留作以后参考。


!在命令中 添加a 将反转返回代码。

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command     # bash: bogus_command: command not found
echo $?                # 127

! ls | bogus_command   # bash: bogus_command: command not found
echo $?                # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #

1
我认为这无关。在您的示例中,我想知道的退出代码ls,而不要反转bogus_command
Michael Mrozek

2
我建议退回该答案。
maxschlepzig 2011年

3
好吧,看来我是个白痴。我实际上已经在脚本中使用过它,然后才认为它可以满足OP的要求。好东西我没有用到任何重要的东西
Falmarri 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.