unix 'tee'和退出状态

ffx8fchx  于 2023-03-18  发布在  Unix
关注(0)|答案(9)|浏览(371)

tee捕获正在执行的命令的标准输出和标准错误,并以与处理的命令相同的退出状态退出,是否有替代tee的方法?
如下所示:

eet -a some.log -- mycommand --foo --bar

其中“eet”是“tee”的一个假想替代:)(-a 表示append,--分隔捕获的命令).破解这样的命令应该不难,但也许它已经存在了,而我没有意识到它?

w8biq8rn

w8biq8rn1#

这适用于Bash:

(
  set -o pipefail
  mycommand --foo --bar | tee some.log
)

圆括号的作用是将pipefail限制在一个命令中。
bash(1) man page
除非启用了pipefail选项,否则管道的返回状态是最后一个命令的退出状态。如果启用了pipefail,则管道的返回状态是要以非零状态退出的最后一个(最右侧)命令的值,如果所有命令都成功退出,则返回状态为零。

kyvafyod

kyvafyod2#

我在 * Capture Exit Code Using Pipe & Tee * 上偶然发现了一些有趣的解决方案。

  1. Bash中有$PIPESTATUS变量:
false | tee /dev/null
[ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS
  1. Perl中“eet”的最简单原型如下所示:
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;
disho6za

disho6za3#

这是一个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很不错,但我记得它们是在Python 3.1左右引入的。)

bd1hkmkf

bd1hkmkf4#

这是我认为最好的纯Bourne shell解决方案,可以用作构建“eet”的基础:

# 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通过管道传输到command 2(echo的输出从未到达command 2,因为我们将其发送到文件描述符3而不是管道读取的文件描述符1)。然后我们将command2的输出重定向到文件描述符4,因此它也不属于文件描述符1-因为我们希望在将文件描述符3上的echo输出返回到文件描述符1时清除文件描述符1,以便命令替换(反勾号)可以捕获它。
最后一点神奇之处是我们第一次把exec 4>&1作为一个单独的命令--它把文件描述符4作为一个外部shell的stdout的副本打开。命令替换将从标准输出中的命令的Angular 捕获写在标准输出上的任何内容--但是,由于就命令替换而言,command2的输出将指向文件描述符4,命令替换并没有捕获它--然而,一旦它从命令替换中“出来”,它实际上仍然会转到脚本的整个文件描述符1。
(The exec 4>&1必须是一个单独的命令才能与许多常见的shell一起工作。在一些shell中,如果你只是把它放在变量赋值的同一行上,在替换的结束反勾之后,它就可以工作。)
(我在示例中使用了复合命令({ ... }),但是subshell(( ... ))也可以工作。subshell只会导致子进程的冗余forking和wait,因为管道的每一端和命令替换的内部通常都暗示了子进程的fork和wait。我不知道有哪个shell被编码为可以跳过其中一个分支,因为它已经完成或将要完成另一个分支。)
您可以用一种技术性较低、更有趣的方式来看待它,就好像命令的输出是相互跳跃的:command1通过管道传送到command2,然后echo的输出跳过command2使得command2不捕捉它,然后command2的输出跳过并跳出命令替换,就在echo刚好及时到达以被替换捕捉使得它在变量中结束时,并且command2的输出继续其到标准输出的路径,就像在普通管道中一样。
另外,据我所知,在这个命令的末尾,$?仍然包含管道中第二个命令的返回代码,因为变量赋值、命令替换和复合命令对于它们内部命令的返回代码都是透明的,所以command2的返回状态应该传播出去。
需要注意的是,command1可能会在某个时候使用文件描述符3或4,或者command2或后面的任何命令将使用文件描述符4,因此为了更卫生,我们可以这样做:

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

命令从启动它们的进程中继承文件描述符,因此整个第二行将继承文件描述符4,而3>&1后面的复合命令将继承文件描述符3,因此4>&-确保内部复合命令不会继承文件描述符4。并且3>&-确保command1不会继承文件描述符3,因此command 1得到了一个“更干净”、更标准的环境。您还可以将内部的4>&-移到3>&-旁边,但我想为什么不尽可能地限制它的范围。
几乎没有程序直接使用预先打开的文件描述符3和4,所以您几乎不必担心它,但后者可能是最好记住并用于通用情况。

bmp9r5qi

bmp9r5qi5#

{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret)
pbgvytdp

pbgvytdp6#

KornShell,* 全部 * 在一行中:

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
inkz8wg9

inkz8wg97#

我还在寻找一个在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]}"
xdnvmnnf

xdnvmnnf8#

#!/bin/sh
logfile="$1"
shift
exec 2>&1
exec "$@" | tee "$logfile"

希望这对你有用。

rslzwgfq

rslzwgfq9#

假设Bash或Z壳(zsh),

my_command >>my_log 2>&1

注意:将标准误差重定向和复制到标准输出的顺序是非常重要的!
我没有意识到你也想在屏幕上看到输出。这当然会将所有输出定向到文件 my_log

相关问题