这个适用于Bash:
(
set -o pipefail
mycommand --foo --bar | tee some.log
)
括号的作用是将pipefail
的影响限制在单个命令上。
从bash(1)手册中可知:
如果未启用pipefail
选项,则管道的返回状态为最后一个命令的退出状态。如果启用了pipefail
选项,则管道的返回状态是最后一个(最右边的)非零状态命令的值,或者如果所有命令都成功退出则为零。
我在PerlMonks上发现了一些有趣的解决方案,可以使用管道和tee命令捕获退出代码。
There is the $PIPESTATUS variable available in Bash:
false | tee /dev/null
[ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS
And the simplest prototype of "eet" in Perl may look as follows:
open MAKE, "command 2>&1 |" or die;
open (LOGFILE, ">>some.log") or die;
while (<MAKE>) {
print LOGFILE $_;
print
}
close MAKE; # To get $?
my $exit = $? >> 8;
close LOGFILE;
这是一个eet
。它可以在我手头上能找到的所有Bash版本中使用,从2.05b到4.0。
#!/bin/bash
tee_args=()
while [[ $# > 0 && $1 != -- ]]; do
tee_args=("${tee_args[@]}" "$1")
shift
done
shift
# now ${tee_args[*]} has the arguments before --,
# and $* has the arguments after --
# redirect standard out through a pipe to tee
exec | tee "${tee_args[@]}"
# do the *real* exec of the desired program
exec "$@"
(pipefail
和$PIPESTATUS
很不错,但我记得它们是在3.1或附近引入的。)
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; echo $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.
command1
会执行并将其常规输出打印在stdout(文件描述符1)上,然后一旦它完成,echo
将执行并在其stdout上打印command1
的退出代码,但是该stdout被重定向到文件描述符3。command1
正在运行时,它的stdout正在被管道传输到command2(因为我们将其发送到文件描述符3而不是1,这是管道读取的内容,所以echo
的输出永远不会传递给command2)。然后,我们将command2
的输出重定向到文件描述符4,以使其也不进入文件描述符1,因为我们希望文件描述符1清除,以便我们将文件描述符三中的echo
输出带回到文件描述符1中,以便命令替换(反引号)可以捕获它。exec 4>&1
- 它将文件描述符四打开为外部shell的stdout的副本。命令替换将捕获从其内部命令的角度写入标准输出的任何内容 - 但是,由于command2
的输出就像命令替换所关心的一样进入文件描述符四,因此命令替换不会捕获它 - 但是,一旦它“出”了命令替换,它实际上仍然会进入脚本的整体文件描述符1。exec 4>&1
必须是单独的命令才能与许多常见的shell一起使用。在某些shell中,如果您将其放在变量赋值的同一行上,在替换的关闭反引号之后,它也可以工作。){ ... }
),但子shell(( ... )
)也可以工作。子shell只会导致冗余的fork和等待子进程,因为管道的每一侧和命令替换的内部通常都意味着fork和等待子进程,并且我不知道任何编码的shell能够识别它可以跳过其中一个fork,因为它已经完成或即将完成另一个fork。)command1
传输到command2
,然后echo
的输出跳过command2
,以便command2
不会捕获它,然后command2
的输出跳过并从命令替换中出来,正好在echo
及时到达以被替换所捕获,以便最终出现在变量中,command2
的输出按照正常管道的方式继续前进。$?
仍将包含管道中第二个命令的返回代码,因为变量赋值、命令替换和复合命令对它们内部命令的返回代码都是透明的,因此 command2
的返回状态应该被传播出去。command1
可能会在某个时候使用文件描述符三或四,或者 command2
或后续的任何命令会使用文件描述符四,为了更加卫生,我们应该这样做:exec 4>&1
exitstatus=`{ { command1 3>&-; echo $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
3>&1
将继承文件描述符三。所以 4>&-
确保内部复合命令不会继承文件描述符四,3>&-
则确保 command1
不会继承文件描述符三,因此 command1 获得了一个更 "干净"、更标准的环境。你也可以将内部的 4>&-
移到 3>&-
旁边,但我认为尽可能限制其范围比较好。
几乎没有程序直接使用预打开的文件描述符三和四,因此你几乎不用担心它们,但后者可能是最好记住并用于通用情况的。
{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret)
{ mycommand --foo --bar; echo $? > exit-code; } | tee some.log; ret=$(cat exit-code)
。是的,写入临时文件有些复杂,但它符合POSIX标准,不像pipefail是bashism,并且在某些情况下也无法正常工作。 - ctruedenKornShell,全部在一行中:
foo; RET_VAL=$?; if test ${RET_VAL} != 0;then echo $RET_VAL; echo Error occurred!>/tmp/out.err;exit 2;fi |tee >>/tmp/out.err ; if test ${RET_VAL} != 0;then exit $RET_VAL;fi
我也在寻找一个适用于AppleScript中的do shell script
的一行代码,它总是使用/bin/sh(由zsh模拟)。这个版本是我找到的唯一一个运行良好的。
mycommand 2>&1 | tee -a output.log; exit ${PIPESTATUS[0]}
或者在 AppleScript 中
set theResult to do shell script "mycommand 2>&1 | tee -a " & quoted form of logFilePath & "; exit ${PIPESTATUS[0]}"
#!/bin/sh
logfile="$1"
shift
exec 2>&1
exec "$@" | tee "$logfile"
zsh
),my_command >>my_log 2>&1
注意:重定向和将标准错误复制到标准输出的顺序很重要!
我没有意识到你想在屏幕上看到输出。这当然会将所有输出都定向到文件my_log中。