我想执行Bash中长时间运行的命令,都捕获它的退出状态,并且发球它的输出。

所以我这样做:

command | tee out.txt
ST=$?

问题在于变量ST捕获了tee而不是命令的退出状态。 我该如何解决?

请注意,命令运行时间长,将输出重定向到文件以供以后查看对于我来说不是一个好的解决方案。


#1楼

哑巴解决方案:通过命名管道(mkfifo)连接它们。 然后可以第二次运行该命令。

 mkfifo pipetee out.txt < pipe &command > pipeecho $?

#2楼

有一个数组可以为您提供管道中每个命令的退出状态。

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1

#3楼

有一个内部Bash变量$PIPESTATUS ; 它是一个数组,用于保存最后一个命令前台管道中每个命令的退出状态。

<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

或者也可以与其他shell(例如zsh)一起使用的另一种替代方法是启用pipefail:

set -o pipefail
...

第一个选项工作zsh由于一点点不同的语法。


#4楼

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

情况:

someprog | filter

要从退出状态someprog和输出filter

这是我的解决方案:

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

在unix.stackexchange.com上,对于相同的问题,请参见我的回答,以获取详细的说明以及不包含subshel​​l和一些警告的替代方法。


#5楼

通过将PIPESTATUS[0]与在子shell中执行exit命令的结果结合起来,您可以直接访问初始命令的返回值:

command | tee ; ( exit ${PIPESTATUS[0]} )

这是一个例子:

# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"

会给你:

return value: 1


#6楼

必须在pipe命令返回后立即将PIPESTATUS [@]复制到数组。 任何读取PIPESTATUS [@]的操作都会擦除其中的内容。 如果计划检查所有管道命令的状态,请将其复制到另一个阵列。 “ $?” 与“ $ {PIPESTATUS [@]}”的最后一个元素的值相同,并且读取它似乎会破坏“ $ {PIPESTATUS [@]}”,但是我还没有完全验证这一点。

declare -a PSA
cmd1 | cmd2 | cmd3
PSA=( "${PIPESTATUS[@]}" )

如果管道在子壳中,则将无法使用。 为了解决这个问题,
在反引号命令中看到bash pipestatus?


#7楼

使用bash的set -o pipefail很有帮助

pipefail:管道的返回值是最后一个以非零状态退出的命令的状态;如果没有以非零状态退出的命令,则返回零


#8楼

在Ubuntu和Debian中,您可以apt-get install moreutils 。 它包含一个名为mispipe的实用程序,该实用程序返回管道中第一个命令的退出状态。


#9楼

纯壳解决方案:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

现在,第二catfalse代替:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

请注意,第一只猫也会失败,因为它的标准输出已关闭。 在此示例中,日志中失败命令的顺序是正确的,但不要依赖它。

此方法允许捕获单个命令的stdout和stderr,因此,如果发生错误,则可以将其也转储到日志文件中;如果没有错误,则可以将其删除(例如dd的输出)。


#10楼

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

# 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上打印icommand1的退出代码,但是该stdout重定向到文件描述符3。

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

魔术的最后一点是,我们exec 4>&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>&-

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

命令从启动它们的进程中继承文件描述符,因此整行第二行将继承文件描述符4,而后跟3>&1的复合命令将继承文件描述符3。 因此, 4>&-确保内部复合命令不会继承文件描述符4,而3>&-将不会继承文件描述符3,因此command1获得了一个“更干净”的标准环境。 你也可以移动内4>&-旁边3>&-但我想,为什么不只是限制它的范围尽可能地。

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


#11楼

基于@ brian-s-wilson的答案; 这个bash辅助函数:

pipestatus() {local S=("${PIPESTATUS[@]}")if test -n "$*"then test "$*" = "${S[*]}"else ! [[ "${S[@]}" =~ [^0\ ] ]]fi
}

因此使用:

1:get_bad_things必须成功,但是不产生任何输出; 但我们希望看到它确实产生的输出

get_bad_things | grep '^'
pipeinfo 0 1 || return

2:所有管道必须成功

thing | something -q | thingy
pipeinfo || return

#12楼

在bash之外,您可以执行以下操作:

bash -o pipefail  -c "command1 | tee output"

例如,在忍者脚本中,该外壳应该是/bin/sh这很有用。


#13楼

使用外部命令而不是深入研究bash的细节有时可能更简单明了。 管道 ,从最小的过程脚本语言execline ,以第二个命令的返回代码*退出,就像sh管道一样,但是与sh不同,它允许反转管道的方向,以便我们可以捕获管道的返回代码。生产者进程(以下全部位于sh命令行中,但execline安装execline ):

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt
hello world$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1$ pipeline -w tee out.txt "" true; echo $?
0$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

使用pipeline与本地bash pipeline具有相同的差异,与答案中使用的bash进程替代相同#43972501 。

*除非有错误,否则实际上pipeline根本不会退出。 它执行到第二个命令中,因此它是第二个执行返回的命令。


#14楼

在纯bash中执行此操作的最简单方法是使用进程替代而不是管道。 有几个区别,但是对于您的用例来说,它们可能并不重要:

  • 在运行管道时,bash等待直到所有进程完成。
  • 将Ctrl-C发送到bash使其杀死管道的所有进程,而不仅仅是主要进程。
  • pipefail选项和PIPESTATUS变量与进程替换无关。
  • 可能更多

通过流程替换,bash只是启动了流程而忘记了它,它甚至在jobs都不可见。

除了提及的差异, consumer < <(producer)producer | consumer producer | consumer基本上是等效的。

如果要翻转哪一个是“主要”过程,只需将命令和替换方向翻转到producer > >(consumer) 。 在您的情况下:

command > >(tee out.txt)

例:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

正如我所说的,管道表达式有一些区别。 除非对管道关闭敏感,否则该过程可能永远不会停止运行。 特别是,它可能不断将内容写入标准输出,这可能会造成混淆。


#15楼

(command | tee out.txt; exit ${PIPESTATUS[0]})

与@cODAR的答案不同,这将返回第一个命令的原始退出代码,不仅返回0(成功)和127(失败)。 但是正如@Chaoran指出的那样,您可以仅调用${PIPESTATUS[0]} 。 但是,将所有内容放在方括号中非常重要。

Bash中的管道输出和捕获退出状态相关推荐

  1. bash grep 判断_bash 退出状态与条件判断

    摘自<高级bash脚本编程指南> 退出状态和退出状态码 exit 被用来结束一个脚本, 就像在C语言中一样. 它也返回一个值, 并且这个值会传递给脚本的父进程, 父进程会使用这个值做下一步 ...

  2. bash-shell高级编程--退出和退出状态码

    退出和退出状态码 大家都知道exit是用来退出一个脚本,既可以使用exit来结束一个脚本,像常用的C语言一样,它也会返回一个值,这个值会传递给脚本的父进程,父进程会使用这个值做下一步的处理. 其实在l ...

  3. ksh 命令退出状态

    -- Start 每个 Linux 命令,脚本或函数都有一个退出状态,它用来指示该命令,脚本或函数是否执行成功.0 代表成功,其他值(1-255) 代表失败. 退出状态 描述 1-125 命令执行失败 ...

  4. 如何在bash中等待多个子进程完成并在任何子进程以代码!= 0结尾时返回退出代码!= 0?

    如何在bash脚本中等待从该脚本派生的多个子进程完成并返回退出代码!= 0,当任何子进程以代码!= 0结尾时? 简单脚本: #!/bin/bash for i in `seq 0 9`; dodoCa ...

  5. linux从Mac下载文件,如何将命令的输出保存到Bash中的文件(也称为Linux和macOS终端) | MOS86...

    当您在bash提示符下运行命令时,通常将该命令的输出直接打印到终端,以便您立即读取.但是bash也允许你 这可以在任何操作系统的bash中运行,从Linux和macOS到Windows 10 选项On ...

  6. redis 启动无输出_Git Bash 中执行交互式命令无响应

    众所周知的原因,Windows下的命令行工具CMD非常难用,所以大家一般用Git Bash作为替代品,但是有时候在其中执行一些命令会出现无响应(无输出结果.无报错信息)的现象,本文将给出该现象的原因及 ...

  7. bash shell中的退出状态码

    shell中运行的每个命令都使用退出状态码告诉shell它已经运行完毕.提出状态码是一个0-255的整数值,在命令结束运行时有命令传给shell.可以捕获这个值并在脚本中使用. Linux提供了一个专 ...

  8. Bash 中的特殊字符大全

    Linux下无论如何都是要用到shell命令的,在Shell的实际使用中,有编程经验的很容易上手,但稍微有难度的是shell里面的那些个符号,各种特殊的符号在我们编写Shell脚本的时候如果能够用的好 ...

  9. linux在哪里储存变量值,关于linux:在bash中,如何在变量中存储返回值?

    我知道Linux中一些非常基本的命令,并且正在尝试编写一些脚本. 我编写了一个函数,用于评估5位数字中最后2位数字的总和. 该函数应将最后的两位数之和连接起来并返回. 我想返回此值的原因是因为我将在其 ...

最新文章

  1. 使用pgpool-ii建立PostgreSQL链接池
  2. Ecplise SVN 配置和使用
  3. html加javascript和canvas类似超级玛丽游戏
  4. 多样性算法在58部落的实践和思考
  5. mysql初体验学习笔记_MySQL数据库初体验
  6. 前端学习(2954):vue文件的三大组成部分
  7. c语言 已知某系统在通信联络中,数据结构(习题)..doc
  8. Qt工作笔记-QStylePlugin插件实现变化窗体背景
  9. Kafka内核理解:消息的收集/消费机制
  10. raft算法_学习分布式一致性协议:自己实现一个Raft算法
  11. Android Mms 数据库
  12. search engine
  13. Python飞机大战代码
  14. Zen Coding插件
  15. 读JavaScript高级程序设计感受
  16. 数据库索引类型介绍及其优缺点、区别、适用场景
  17. Arduino资源下载
  18. python爬虫系列——拉勾网
  19. ESP32学习笔记(23)——NVS(非易失性存储)接口使用
  20. Java中将汉语转成拼音的方法

热门文章

  1. 如何对比两个Jar包
  2. Android Launcher3(二) -- Drag拖动实现
  3. java nio使用_什么时候使用NIO?
  4. 一个接口查3张表数据_“离婚冷静期”倒计时,天眼查专业版数据显示我国目前有3万余家婚姻咨询相关企业 北京市最多...
  5. RxJS 6有哪些新变化?
  6. 2007年3月东北微软技术活动预告
  7. hashlib模块,md5加密
  8. PHP 端口号 是否 被占用 以及 解决方法
  9. 今日课堂学习笔记01
  10. ubuntu下启动和关闭tomcat的简单方法