关闭stderr并将消息重定向到stdout



我正在使用TCL Expect,并在脚本中执行脚本。是否可以关闭stderr流并将所有其他消息写入stdout?

set runcmd [ exec $SCRIPTS_PATH/config $build_tag -u 2>&- >&stdout]
# Just in case...
if { [catch $runcmd res] } {
send_user "Failed to run command due to: $res"
}

上面代码的当前行为不会向stdout显示任何内容,但会避免并不显示stderr。

Tcl(and expect)exec命令定义自己的重定向:请参阅http://tcl.tk/man/tcl8.5/TclCmd/exec.htm--您不能仅仅假设sh/bash重定向会起作用。

你想要

if { [catch {exec $SCRIPTS_PATH/config $build_tag -u 2>/dev/null} res] != 0 } {
send_user "Failed to run command due to: $res"
}

如果要将stderr发送到stdout,请使用2>@1(请参阅手册页)

如果您想将命令声明为变量,请执行以下操作:

set runcmd [list exec $SCRIPTS_PATH/config $build_tag -u 2>/dev/null]
if { [catch $runcmd res] != 0 } {
send_user "Failed to run command due to: $res"
}

如果你真的想使用shell重定向,你必须在shell中执行命令:

set runcmd [list exec sh -c "$SCRIPTS_PATH/config $build_tag -u 2>&1"]

我看不出2>&-在bash手册页中是有效的:不过n<&-关闭了文件描述符n

要抑制stderr,可以执行以下操作:

exec config $build_tag -u 2>/dev/null

如果我运行的命令想要将不相关的垃圾吐到我想要抑制的stderr,我会用2>/dev/null放弃它。

# The “list” is important! It says “build a list here” instead of direct evaluation
set runcmd [list $SCRIPTS_PATH/config $build_tag -u]
exec {*}$runcmd 2>/dev/null

如果$SCRIPTS_PATH/config有一个非零的退出代码,这仍然会产生错误,但这通常是正确的。捕捉它:

if {[catch { exec {*}$runcmd 2>/dev/null }]} {
# An error has been trapped
}

Tcl没有提供在stderr完全关闭的情况下运行子流程的方法;这真是一种奇怪的状态。重定向到/dev/null更有可能是有用和理智的。

另一方面,如果您希望在生成这些消息的情况下运行到外部Tcl脚本的stderr的错误,而不会导致错误(exec的一个非常令人恼火的特性),那么exec的任何错误都只来自子进程退出代码,这是通过不同的重定向来完成的,比如:

exec {*}$runcmd 2>@stderr

这是因为exec通常会捕获子进程stderr,以用作更好的错误消息源。这一切都变得更加复杂,因为有些命令使用stderr打印错误消息,但实际上并没有正确输出,而另一些命令则将日志记录信息写入stderr,而不仅仅是错误消息。(这一切都有点像沼泽,这是许多人长期以来共同编写代码的结果。Tcl只是试图在这个领域做它能做的事情;我不相信它做得对,但过去的设计选择因现在依赖它们的脚本数量而神圣化。)


不会做的是在list命令调用中放入exec或重定向;我觉得这太令人困惑了。我更喜欢更直接地表达"属于Tcl的东西"。这只是品味的问题,但我认为它更清晰。它还让我可以考虑构建命令,然后将其发射给下级炮弹进行评估;内部语法不同(shell,而不是Tcl),但您可以执行以下操作:

set runcmd "$SCRIPTS_PATH/config $build_tag -u"
exec /bin/sh -c $runcmd 2>/dev/null

这在逻辑上是相当等价的,对某些事情来说更好。


如果您仍在使用8.4(或更早版本!),则将无法使用{*}语法。如果$runcmd是由list构建的,那么作为一种替代方案,您可以这样做。

eval exec $runcmd 2>/dev/null

理论上,你实际上应该写:

eval [list exec] [lrange $runcmd 0 end] [list 2>/dev/null]

但这是非常糟糕的,而且只有当$runcmd可能不是规范格式列表时才有必要这样做。(我们在8.5中添加了列表扩展语法,因为我们再也不想看到那种怪物了,而且我们发现在任何情况下都很难足够小心,草率会导致错误。)

如果你正在使用8.4,并且没有严格要求你坚持使用,那么至少升级到8.5;8.4不再支持。(我们确实支持了十多年…)8.5与8.4非常兼容,但值得检查您的代码是否真的有效。小心是值得的。如果你遇到了问题,可以在Stack Overflow上问一个关于修复它的问题。尽管如此,当我迁移它们时,我的脚本都只是工作,所以值得一试。

最新更新