“T恤” 和退出状态

发布于 07-24 08:48 字数 524 浏览 14 评论 0原文

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

如下所示:

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

其中“eet”是“tee”的想象替代品:)(-a表示追加,--分隔捕获的命令)。 破解这样的命令应该不难,但也许它已经存在而我不知道?

Is there an alternative to tee which captures standard output and standard error of the command being executed and exits with the same exit status as the processed command?

Something like the following:

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

Where "eet" is an imaginary alternative to "tee" :) (-a means append and -- separates the captured command). It shouldn't be hard to hack such a command, but maybe it already exists and I'm not aware of it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

谈情不如逗狗2024-07-31 08:48:03

这适用于 Bash:

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

括号将管道故障的影响限制为仅一个命令。

bash(1) 手册页

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

This works with Bash:

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

The parentheses are there to limit the effect of pipefail to just the one command.

From the bash(1) man page:

The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully.

静水深流2024-07-31 08:48:03

我在 Capture Exit Code using Pipe & 中偶然发现了一些有趣的解决方案。 T恤

  1. Bash 中有可用的 $PIPESTATUS 变量:

    <前><代码>假 | 三通/dev/null
    [ $PIPESTATUS -eq 0 ] || 退出$PIPESTATUS

  2. Perl 中“eet”的最简单原型可能如下所示:

    打开 MAKE,“命令 2>&1 |”   或者死; 
      open (LOGFILE, ">>some.log") or die; 
      while () {  
          打印日志文件 $_;  
          打印  
      } 
      关闭MAKE;   # 获得$? 
      我的 $exit = $?   >>>   8; 
      关闭日志文件; 
      

I stumbled upon a couple of interesting solutions at Capture Exit Code Using Pipe & Tee.

  1. There is the $PIPESTATUS variable available in Bash:

    false | tee /dev/null
    [ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS
    
  2. 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;
    
昔日梦未散2024-07-31 08:48:03

这是一个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 左右引入的。)

Here's an eet. Works with every Bash I can get my hands on, from 2.05b to 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 and $PIPESTATUS are nice, but I recall them being introduced in 3.1 or thereabouts.)

太阳哥哥2024-07-31 08:48:03

这是我认为最好的纯 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被重定向到文件描述符三。

command1 运行时,它的 stdout 会通过管道传输到 command2(echo 的输出永远不会到达 command2,因为我们将其发送到文件描述符 3 而不是 1,即管道读取的内容)。 然后我们将command2的输出重定向到文件描述符4,这样它也不会出现在文件描述符1之外——因为我们希望当我们带来echo时文件描述符1被清除文件描述符三上的输出返回到文件描述符一中,以便命令替换(反引号)可以捕获它。

最后一点神奇之处在于,我们将第一个 exec 4>&1 作为一个单独的命令执行 - 它打开文件描述符 4 作为外部 shell 标准输出的副本。 命令替换将从标准输出内部命令的角度捕获在标准输出上写入的所有内容 - 但是,由于就命令替换而言,command2 的输出将写入文件描述符 4,因此命令替换不会捕获它——但是,一旦它“脱离”命令替换,它实际上仍然会转到脚本的整体文件描述符之一。

exec 4>&1 必须是一个单独的命令才能与许多常见的 shell 一起使用。在某些 shell 中,如果您只是将它放在与变量赋值相同的行上,在结束之后,它就可以工作替换的反引号。)

(在我的示例中,我使用复合命令 ({ ... }),但子 shell (( ... )) 也可以工作。 subshel​​l 只会导致子进程的冗余分叉和等待,因为管道的每一侧和命令替换的内部通常已经意味着子进程的分叉和等待,并且我不知道正在编码任何 shell认识到它可以跳过其中一个分叉,因为它已经完成或即将执行另一个分叉。)

您可以以一种技术含量较低且更有趣的方式来看待它,就好像命令的输出正在相互跨越:< code>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 将继承文件描述符三。 因此 4>&- 确保内部复合命令不会继承文件描述符 4,而 3>&- 确保 command1< /code> 不会继承文件描述符三,因此 command1 获得一个“更干净”、更标准的环境。 您还可以将内部 4>&- 移到 3>&- 旁边,但我想为什么不尽可能限制其范围。

几乎没有程序直接使用预打开的文件描述符三和四,因此您几乎永远不必担心它,但最好记住后者并将其用于通用情况。

This is what I consider to be the best pure-Bourne-shell solution to use as the base upon which you could build your "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.

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, echo will execute and print command1's exit code on its stdout, but that stdout is redirected to file descriptor three.

While command1 is running, its stdout is being piped to command2 (echo'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 one – because we want file descriptor one clear for when we bring the echo output on file descriptor three back down into file descriptor one so that the command substitution (the backticks) can capture it.

The final bit of magic is that first exec 4>&1 we did as a separate command – it opens file descriptor four 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 four 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 one.

(The exec 4>&1 has to be a separate command to work with many common shells. In some shells it works if you just put it on the same line as the variable assignment, after the closing backtick of the substitution.)

(I use compound commands ({ ... }) in my example, but subshells (( ... )) would also work. The subshell will just cause a redundant forking and awaiting of a child process, since each side of a pipe and the inside of a command substitution already normally implies a fork and await of a child process, and I don't know of any shell being coded to recognize that it can skip one of those forks because it's already done or is about to do the other.)

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 echo's output jumps over command2 so that command2 doesn't catch it, and then command2's output jumps over and out of the command substitution just as echo 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 way to the standard output, just as in a normal pipe.

Also, as I understand it, at the end of this command, $? 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.

A caveat is that it is possible that command1 will at some point end up using file descriptors three or four, or that command2 or any of the later commands will use file descriptor four, so to be more hygienic, we would do:

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

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>&- makes sure that command1 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.

Almost no programs uses pre-opened file descriptor three and four directly, so you almost never have to worry about it, but the latter is probably best to keep in mind and use for general-purpose cases.

围归者2024-07-31 08:48:03
{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret)
{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret)
不爱素颜2024-07-31 08:48:03

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

KornShell, all in one line:

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
风轻花落早2024-07-31 08:48:03

我还在寻找一种可以在 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]}"

I was also looking for a one-liner that works in do shell script in AppleScript, which always uses /bin/sh (emulated by zsh). This version is the only one I found that works well:

mycommand 2>&1 | tee -a output.log; exit ${PIPESTATUS[0]}

or in AppleScript

set theResult to do shell script "mycommand 2>&1 | tee -a " & quoted form of logFilePath & "; exit ${PIPESTATUS[0]}"
转身以后2024-07-31 08:48:03
#!/bin/sh
logfile="$1"
shift
exec 2>&1
exec "$@" | tee "$logfile"

希望这对你有用。

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

Hopefully this works for you.

爱的那么颓废2024-07-31 08:48:03

假设使用 Bash 或 Z shell (zsh),

my_command >>my_log 2>&1

请注意,标准错误的重定向和复制到标准输出的顺序非常重要!

我没有意识到您也想在屏幕上看到输出。 这当然会将所有输出定向到文件 my_log

Assuming Bash or Z shell (zsh),

my_command >>my_log 2>&1

N.B. The sequence of redirection and duplication of standard error onto standard output is significant!

I didn't realise you wanted to see the output on screen as well. This will of course direct all output to the file my_log.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文