Pipe output and capture exit status in Bash

2018-12-31 09:05发布

I want to execute a long running command in Bash, and both capture its exit status, and tee its output.

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?

Note that command is long running and redirecting the output to a file to view it later is not a good solution for me.

15条回答
倾城一夜雪
2楼-- · 2018-12-31 09:53

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.

查看更多
零度萤火
3楼-- · 2018-12-31 09:55

The simplest way to do this in plain bash is to use process substitution instead of a pipeline. There are several differences, but they probably don't matter very much for your use case:

  • 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 the PIPESTATUS variable are irrelevant to process substitution.
  • Possibly more

With process substitution, bash just starts the process and forgets about it, it's not even visible in jobs.

Mentioned differences aside, consumer < <(producer) and producer | consumer are essentially equivalent.

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:

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.

查看更多
何处买醉
4楼-- · 2018-12-31 09:57

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:

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

查看更多
登录 后发表回答