发球和退出状态

时间:2009-06-12 09:44:03

标签: unix shell tee

是否有“tee”的替代方法,它捕获正在执行的命令的STDOUT / STDERR,并以与处理的命令相同的退出状态退出。如下:

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

其中“eet”是“tee”:)的虚构替代品(-a表示追加, - 分隔捕获的命令)不应该很难破解这样的命令但是它可能已经存在并且我是不知道吗?

感谢。

8 个答案:

答案 0 :(得分:22)

这适用于bash:

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

括号用于限制pipefail对一个命令的影响。

来自bash(1)手册页:

除非启用pipefail选项,否则管道的返回状态是最后一个命令的退出状态。如果启用了pipefail,则管道的返回状态是以非零状态退出的最后(最右边)命令的值,如果所有命令都成功退出,则返回零。

答案 1 :(得分:9)

在这里偶然发现了几个有趣的解决方案http://www.perlmonks.org/?node_id=597613

1)bash中有$ PIPESTATUS变量:

   false | tee /dev/null
   [ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS

2)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;

答案 2 :(得分:5)

这是一个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或其附近引入的。)

答案 3 :(得分:1)

Korn shell,ALL in 1行:

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

答案 4 :(得分:1)

我认为这是最好的纯Bourne-shell解决方案,可用作构建您的&#34; eet&#34;的基础:

# 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将执行并打印command1&#39; s退出其stdout上的代码,但该stdout被重定向到文件描述符3。

当command1正在运行时,它的stdout被传送到command2(printf&#39; s输出永远不会使它成为command2,因为我们将它发送到文件描述符3而不是1,这是管道读取的内容)。然后我们将command2的输出重定向到文件描述符4,这样它也不会出现在文件描述符1之外 - 因为我们希望稍后释放文件描述符1,因为我们将把文件描述符3上的printf输出带回来下到文件描述符1 - 因为命令替换(反引号)将捕获什么,以及将被置于变量中的内容。

魔术的最后一点是我们作为单独命令执行的第一个exec 4>&1 - 它打开文件描述符4作为外部shell的标准输出的副本。命令替换将从其内部的命令的角度捕获在标准输出上写入的任何内容 - 但是,由于命令替换涉及命令替换,因此命令2的输出将转到文件描述符4。捕获它 - 然而,一旦它得到&#34; out&#34;对于命令替换,它实际上仍然是脚本的整个文件描述符1。

exec 4>&1必须是一个单独的命令,因为当你尝试写入命令替换中的文件描述符时,许多常见的shell都不喜欢它,这是在&#34;使用替换的外部&#34;命令。所以这是最简单的可移植方式。)

您可以用技术性较低且更有趣的方式查看它,就像命令的输出相互跳跃一样:command1管道到命令2,然后printf的输出跳过命令2,这样命令2就不会#39; t捕获它,然后命令2的输出跳过命令替换,就像printf及时降落以被替换捕获一样,以便它最终在变量中,并命令2&#39 ; s输出以正常的方式写入标准输出,就像在普通管道中一样。

另外,据我所知,$?仍将包含管道中第二个命令的返回码,因为变量赋值,命令替换和复合命令对命令的返回码都是有效透明的在它们内部,所以command2的返回状态应该传播出来。

需要注意的是,command1可能会在某些时候最终使用文件描述符3或4,或者command2或任何后面的命令将使用文件描述符4,因此为了更加健壮,会这样做:

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

请注意,我在我的示例中使用了复合命令,但子shell(使用( )而不是{ }也可以使用,但可能效率较低。)

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

我不确定直接使用文件描述符三和四的频率 - 我认为大多数时候程序都使用系统调用来返回当前未使用的文件描述符,但有时代码写入文件直接描述符3,我想(我可以想象一个程序检查一个文件描述符,看看它是否打开,如果是,则使用它,如果不是则表现不同)。所以后者可能最好记住并用于通用案例。

---本行以外的内容---

由于历史原因,这是我原来的,不可移植到所有贝壳的答案:

[编辑]我的不好,这不适用于bash因为bash在摆弄文件描述符时需要额外的溺爱,我会尽快更新。 [/编辑]

Pure Bourne shell解决方案:

exitstatus=`{ 3>&- command1; } 1>&3; printf $?` 3>&1 | command2
# $exitstatus now has command1's exit status.

这是您建立自己的基础&#34; eet&#34;。在一些命令行参数解析中解压缩所有这一切,将command2转换为&#34; tee&#34;与相关选项等。

非常详细的解释如下:

在顶层,语句只是两个命令之间的管道:

commandA | command2

commandA又分解为单个命令,将文件描述符3重定向到文件描述符1(stdout):

commandB 3>&1

这意味着shell将期望commandB将某些内容写入文件描述符3 - 如果文件描述符3从未打开过,那将是一个错误。这也意味着command2将在文件描述符1(stdout)和3上获得任何commandB输出。

commandB又是一个使用命令替换的变量赋值:

VAR_FOO=`commandC`

我们知道变量赋值不会在任何文件描述符上打印任何内容(并且为了替换而捕获了commandC&#; sdout),因此我们知道commandB作为一个整体不会在stdout上输出任何内容。因此,command2只会看到commandC写入文件描述符3的内容。

commandC是两个命令,第二个命令打印第一个命令的退出状态:

commandD ; printf $?

所以现在我们知道最后一步中的变量赋值将包含commandD的退出状态。

现在,commandD分解为另一个基本的重定向,一个comman的stdout到文件描述符3:

commandE 1>&3

所以现在我们知道写入文件描述符3,最终写入command2的东西是commandE的标准输出。

最后:commandE是一个&#34;复合命令&#34; (你也可以在这里使用子shell,但效率不高),绕过另一种不太常见的&#34;重定向&#34;:

{ 3>&- command1; }

(那3>&-有点棘手,所以我们最后会回到它。)所以当最后一个命令和最后一个括号在同一行时,复合命令强制使用分号,这就是为什么那里。因此我们知道复合命令返回其最后一个命令的退出代码,并且它们继承了其他所有文件描述符,因此我们现在知道command1的stdout流出复合命令,重定向到文件描述符3以避免被捕获通过命令替换,同时命令替换捕获printf的剩余标准输出,一旦完成,它就会回显command1的退出状态。

现在对于棘手的问题:3>&-说&#34;关闭文件描述符3&#34;。您可能会想,&#34;当您将command1的输出重定向到它时,为什么要关闭它?&#34;好吧,如果仔细观察,你会发现复合命令(在大括号内)的close效应只是命令1,而重定向会影响整个复合命令。

所以会发生以下情况:当复合命令的各个命令运行时,shell打开文件描述符3.进程继承文件描述符,因此默认情况下,command1将在文件描述符3打开的情况下运行并指向同样的地方。这很糟糕,因为有时候程序实际上期望特定的文件描述符意味着特殊的东西 - 当文件描述符3打开时它们可能表现不同。最强大的解决方案是只关闭文件描述符3(或者你使用的任何一个数字),因为它只是运行,就像文件描述符3从未打开一样。

答案 5 :(得分:0)

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

答案 6 :(得分:-2)

天儿真好,

假设bash或zsh,

my_command >>my_log 2>&1

N.B。将STDERR重定向和复制到STDOUT的顺序非常重要!

编辑糟糕。没意识到你也希望在屏幕上看到输出。这当然会将所有输出定向到文件my_log。

HTH

欢呼声,

答案 7 :(得分:-2)

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

希望这适合你。