Bash 中的管道输出和捕获退出状态
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1221833/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pipe output and capture exit status in Bash
提问by flybywire
I want to execute a long running command in Bash, and both capture its exit status, and teeits output.
我想执行Bash中长时间运行的命令,都捕获它的退出状态,并且发球它的输出。
So I do this:
所以我这样做:
command | tee out.txt
ST=$?
The problem is that the variable ST captures the exit status of tee
and not of command. How can I solve this?
问题是变量 ST 捕获了tee
命令的退出状态而不是命令的退出状态。我该如何解决这个问题?
Note that command is long running and redirecting the output to a file to view it later is not a good solution for me.
请注意,该命令长时间运行并将输出重定向到文件以供稍后查看对我来说不是一个好的解决方案。
回答by codar
There is an internal Bash variable called $PIPESTATUS
; it's an array that holds the exit status of each command in your last foreground pipeline of commands.
有一个名为$PIPESTATUS
;的内部 Bash 变量。它是一个数组,用于保存最后一个前台命令管道中每个命令的退出状态。
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0
Or another alternative which also works with other shells (like zsh) would be to enable pipefail:
或者另一种也适用于其他 shell(如 zsh)的替代方法是启用 pipefail:
set -o pipefail
...
The first option does notwork with zsh
due to a little bit different syntax.
第一个选项并不能一起工作zsh
,由于一点点不同的语法。
回答by Felipe Alvarez
using bash's set -o pipefail
is helpful
使用 bashset -o pipefail
很有帮助
pipefail: the return value of a pipeline is the status of the last command to exit with a non-zero status, or zero if no command exited with a non-zero status
pipefail:管道的返回值是最后一个以非零状态退出的命令的状态,如果没有命令以非零状态退出,则为零
回答by EFraim
Dumb solution: Connecting them through a named pipe (mkfifo). Then the command can be run second.
愚蠢的解决方案:通过命名管道(mkfifo)连接它们。然后可以第二次运行该命令。
mkfifo pipe
tee out.txt < pipe &
command > pipe
echo $?
回答by Stefano Borini
There's an array that gives you the exit status of each command in a pipe.
有一个数组可以为您提供管道中每个命令的退出状态。
$ 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
回答by lesmana
This solution works without using bash specific features or temporary files. Bonus: in the end the exit status is actually an exit status and not some string in a file.
此解决方案无需使用 bash 特定功能或临时文件即可工作。奖励:最终退出状态实际上是退出状态,而不是文件中的某个字符串。
Situation:
情况:
someprog | filter
you want the exit status from someprog
and the output from filter
.
你想要退出状态someprog
和输出filter
。
Here is my solution:
这是我的解决方案:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
See my answer for the same question on unix.stackexchange.comfor a detailed explanation and an alternative without subshells and some caveats.
请参阅我在 unix.stackexchange.com 上对同一问题的回答以获取详细说明以及没有子外壳和一些警告的替代方案。
回答by par
By combining PIPESTATUS[0]
and the result of executing the exit
command in a subshell, you can directly access the return value of your initial command:
通过在子shell中组合PIPESTATUS[0]
和执行exit
命令的结果,您可以直接访问初始命令的返回值:
command | tee ; ( exit ${PIPESTATUS[0]} )
command | tee ; ( exit ${PIPESTATUS[0]} )
Here's an example:
下面是一个例子:
# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"
will give you:
会给你:
return value: 1
return value: 1
回答by mtraceur
So I wanted to contribute an answer like lesmana's, but I think mine is perhaps a little simpler and slightly more advantageous pure-Bourne-shell solution:
所以我想贡献一个像 lesmana 的答案,但我认为我的可能更简单一些,而且更有利的纯 Bourne-shell 解决方案:
# 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.
I think this is best explained from the inside out - command1 will execute and print its regular output on stdout (file descriptor 1), then once it's done, printf will execute and print icommand1's exit code on its stdout, but that stdout is redirected to file descriptor 3.
我认为最好从内到外解释 - command1 将在标准输出(文件描述符 1)上执行并打印其常规输出,然后一旦完成,printf 将在其标准输出上执行并打印 icommand1 的退出代码,但该标准输出被重定向到3. 文件描述符
While command1 is running, its stdout is being piped to command2 (printf's output never makes it to command2 because we send it to file descriptor 3 instead of 1, which is what the pipe reads). Then we redirect command2's output to file descriptor 4, so that it also stays out of file descriptor 1 - because we want file descriptor 1 free for a little bit later, because we will bring the printf output on file descriptor 3 back down into file descriptor 1 - because that's what the command substitution (the backticks), will capture and that's what will get placed into the variable.
当 command1 正在运行时,它的 stdout 正在通过管道传输到 command2(printf 的输出永远不会将其发送到 command2,因为我们将它发送到文件描述符 3 而不是 1,这是管道读取的内容)。然后我们将 command2 的输出重定向到文件描述符 4,这样它也不会出现在文件描述符 1 之外——因为我们希望文件描述符 1 稍后空闲,因为我们会将文件描述符 3 上的 printf 输出带回文件描述符1 - 因为这是命令替换(反引号)将捕获的内容,这就是将放入变量的内容。
The final bit of magic is that first exec 4>&1
we did as a separate command - it opens file descriptor 4 as a copy of the external shell's stdout. Command substitution will capture whatever is written on standard out from the perspective of the commands inside it - but since command2's output is going to file descriptor 4 as far as the command substitution is concerned, the command substitution doesn't capture it - however once it gets "out" of the command substitution it is effectively still going to the script's overall file descriptor 1.
最后一点神奇的是,首先exec 4>&1
我们作为一个单独的命令执行 - 它打开文件描述符 4 作为外部 shell 的标准输出的副本。命令替换将从其中的命令的角度捕获标准输出上写入的任何内容 - 但由于 command2 的输出将发送到文件描述符 4 就命令替换而言,命令替换不会捕获它 - 但是一旦它从命令替换中“退出”,它实际上仍会转到脚本的整体文件描述符 1。
(The exec 4>&1
has to be a separate command because many common shells don't like it when you try to write to a file descriptor inside a command substitution, that is opened in the "external" command that is using the substitution. So this is the simplest portable way to do it.)
(exec 4>&1
必须是一个单独的命令,因为当您尝试在命令替换中写入文件描述符时,许多常见的 shell 不喜欢它,该命令在使用替换的“外部”命令中打开。所以这是最简单的便携式方法。)
You can look at it in a less technical and more playful way, as if the outputs of the commands are leapfrogging each other: command1 pipes to command2, then the printf's output jumps over command 2 so that command2 doesn't catch it, and then command 2's output jumps over and out of the command substitution just as printf lands just in time to get captured by the substitution so that it ends up in the variable, and command2's output goes on its merry way being written to the standard output, just as in a normal pipe.
你可以用一种不太技术、更有趣的方式来看待它,好像命令的输出是相互跳跃的:command1 管道到 command2,然后 printf 的输出跳过命令 2,这样 command2 就不会捕捉到它,然后命令 2 的输出跳过命令替换,就像 printf 及时着陆以被替换捕获以便它结束在变量中一样,并且命令 2 的输出继续以愉快的方式写入标准输出,就像在普通管道中。
Also, as I understand it, $?
will still contain the return code of the second command in the pipe, because variable assignments, command substitutions, and compound commands are all effectively transparent to the return code of the command inside them, so the return status of command2 should get propagated out - this, and not having to define an additional function, is why I think this might be a somewhat better solution than the one proposed by lesmana.
另外,据我了解,$?
管道中仍然会包含第二条命令的返回码,因为变量赋值、命令替换和复合命令对于其中的命令返回码都是有效透明的,所以返回状态为command2 应该传播出去 - 这就是为什么我认为这可能是比 lesmana 提出的更好的解决方案,而不必定义额外的功能。
Per the caveats lesmana mentions, it's possible that command1 will at some point end up using file descriptors 3 or 4, so to be more robust, you would do:
根据 lesmana 提到的警告,command1 可能会在某个时候最终使用文件描述符 3 或 4,因此为了更健壮,您可以这样做:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
Note that I use compound commands in my example, but subshells (using ( )
instead of { }
will also work, though may perhaps be less efficient.)
请注意,我在示例中使用了复合命令,但使用了子 shell(使用( )
而不是{ }
也可以使用,但可能效率较低。)
Commands inherit file descriptors from the process that launches them, so the entire second line will inherit file descriptor four, and the compound command followed by 3>&1
will inherit the file descriptor three. So the 4>&-
makes sure that the inner compound command will not inherit file descriptor four, and the 3>&-
will not inherit file descriptor three, so command1 gets a 'cleaner', more standard environment. You could also move the inner 4>&-
next to the 3>&-
, but I figure why not just limit its scope as much as possible.
命令从启动它们的进程继承文件描述符,因此整个第二行将继承文件描述符 4,后面的复合命令3>&1
将继承文件描述符 3。因此4>&-
确保内部复合命令不会继承文件描述符 4,并且3>&-
不会继承文件描述符 3,因此 command1 得到一个“更干净”、更标准的环境。您也可以将内部移动到4>&-
旁边3>&-
,但我想为什么不尽可能限制其范围。
I'm not sure how often things use file descriptor three and four directly - I think most of the time programs use syscalls that return not-used-at-the-moment file descriptors, but sometimes code writes to file descriptor 3 directly, I guess (I could imagine a program checking a file descriptor to see if it's open, and using it if it is, or behaving differently accordingly if it's not). So the latter is probably best to keep in mind and use for general-purpose cases.
我不确定直接使用文件描述符 3 和 4 的频率 - 我认为大多数时候程序使用返回未使用的文件描述符的系统调用,但有时代码直接写入文件描述符 3,我猜测(我可以想象一个程序检查文件描述符以查看它是否打开,如果打开则使用它,或者如果没有则相应地表现不同)。所以后者可能最好记住并用于通用情况。
回答by Bryan Larsen
In Ubuntu and Debian, you can apt-get install moreutils
. This contains a utility called mispipe
that returns the exit status of the first command in the pipe.
在 Ubuntu 和 Debian 中,您可以apt-get install moreutils
. 它包含一个被调用的实用程序mispipe
,它返回管道中第一个命令的退出状态。
回答by jakob-r
(command | tee out.txt; exit ${PIPESTATUS[0]})
Unlike @cODAR's answer this returns the original exit code of the first command and not only 0 for success and 127 for failure. But as @Chaoran pointed out you can just call ${PIPESTATUS[0]}
. It is important however that all is put into brackets.
与@cODAR 的答案不同,它返回第一个命令的原始退出代码,而不仅仅是 0 表示成功,127 表示失败。但正如@Chaoran 指出的那样,您只需调用${PIPESTATUS[0]}
. 然而,重要的是所有内容都放在括号中。
回答by clacke
The simplest way to do this in plain bash is to use process substitutioninstead of a pipeline. There are several differences, but they probably don't matter very much for your use case:
在普通 bash 中执行此操作的最简单方法是使用进程替换而不是管道。有几个区别,但它们对于您的用例可能并不重要:
- When running a pipeline, bash waits until all processes complete.
- Sending Ctrl-C to bash makes it kill all the processes of a pipeline, not just the main one.
- The
pipefail
option and thePIPESTATUS
variable are irrelevant to process substitution. - Possibly more
- 运行管道时,bash 会等待所有进程完成。
- 将 Ctrl-C 发送到 bash 使其终止管道的所有进程,而不仅仅是主要进程。
- 的
pipefail
选项和PIPESTATUS
变量无关的过程替代。 - 可能更多
With process substitution, bash just starts the process and forgets about it, it's not even visible in jobs
.
使用进程替换,bash 只是启动进程并忘记它,它甚至在jobs
.
Mentioned differences aside, consumer < <(producer)
and producer | consumer
are essentially equivalent.
提到分歧放在一边,consumer < <(producer)
而producer | consumer
本质上是相同的。
If you want to flip which one is the "main" process, you just flip the commands and the direction of the substitution to producer > >(consumer)
. In your case:
如果要翻转哪个是“主要”进程,只需将命令和替换方向翻转到producer > >(consumer)
. 在你的情况下:
command > >(tee out.txt)
Example:
例子:
$ { 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
As I said, there are differences from the pipe expression. The process may never stop running, unless it is sensitive to the pipe closing. In particular, it may keep writing things to your stdout, which may be confusing.
正如我所说,与管道表达式存在差异。进程可能永远不会停止运行,除非它对管道关闭很敏感。特别是,它可能会不断向您的标准输出写入内容,这可能会令人困惑。