在 Tcl 中跟踪标准输出和标准错误

Tracing stdout and stderr in Tcl

我不确定这是否荒谬。 trace stdoutstderr 的读写在Tcl 中不是可以吗?

我已经尝试了下面的方法,但没有发现任何线索。

% proc tracer {varname args} {
    upvar #0 $varname var
    puts "$varname value : $var"
}
% trace add variable stdout read tracer
% trace add variable stdout write tracer
% puts stdout hai
hai
% puts hai
hai
% trace add variable stderr write tracer
% trace add variable stderr read tracer
% puts hai
hai
% puts stderr hai
hai

根据 puts 的手册页,如果没有为 puts 命令指定 channelId,则默认为 stdout,这意味着即使 [=18] =], stdout 将被访问。正确的 ? (尽管即使参数为 stdoutstderr 也不起作用)

您尝试的解决方案的问题是 stdoutstderrstdin 不是变量,而是所谓的 "channels" 的名称。 本质上,它们位于一个单独的名称空间中(不是由 namespace Tcl 命令操作的名称空间!):您可以使用 chan names 命令获取它们的列表,但您不能,比如说, rename 一个通道,或为其赋值或 unset 它:这些操作在通道上根本没有意义,而是会影响该名称的变量。

跟踪一个通道的一种方法是用另一个——"script-level"——通道和"proxy"所有操作来实际颠覆它。这个技巧使用了一个鲜为人知的 Tcl 特性:当你关闭一个 Tcl 的标准通道并立即打开一个通道(无论是 "real" 还是 "script-level")时,它将被注册以代替该标准通道刚刚关闭。因此,如果我们关闭一个标准频道并立即在其位置创建我们自己的 "proxy" 频道,我们就会颠覆该标准频道。

那些 "script-level" ("reflected") 个通道需要 Tcl ≥ 8.5。

这里是颠覆stdout的草图,需要Linux(支持/proc/self/fd/<fileno>

proc traceChan {cmd chan args} {
    global stdout

    puts stderr "Trace on $chan; cmd=$cmd; args=$args"

    switch -- $cmd {
        initialize {
            return [list initialize finalize watch write configure cget cgetall]
        }
        finalize {
            chan close $stdout
        }
        watch {
            # FIXME: not implemented
        }
        write {
            set data [lindex $args 0]
            chan puts -nonewline $stdout $data
            return [string length $data]
        }
        configure {
            return [chan config $stdout {*}$args]
        }
        cget {
            return [chan cget $stdout {*}$args
        }
        cgetall {
            return [chan configure $stdout]
        }
    }
}

set fn [file readlink /proc/self/fd/1]
set conf [chan config stdout]
chan close stdout
chan create write ::traceChan
set stdout [open $fn w]
chan configure stdout {*}$conf

puts [chan names]
puts test
chan flush stdout
chan close stdout

在我的系统上,它会破坏终端设置(stdout 已连接到终端),并要求我在脚本退出后执行 reset,然后执行 stty sane,但至少它完成工作。

此脚本的实际问题:

  • 事实上,我们对写入的字节数撒谎:我们不知道底层 stdout 通道将写入多少字节,因为它取决于许多因素。
  • 我们根本不处理频道事件。

这些问题可以通过编写实现此类代理的 C 模块来解决:使用 C API,您可以访问基础文件 descriptor/handle(因此不需要 /proc/self/fd/...) 和一个包装它的 Tcl 对象(这样你就可以 clone 它马上)并且知道底层通道写入了多少字节。


哦,请注意,如果您不介意将脚本发送的数据扔到被破坏的标准通道中,那么就不要再重新打开真正的底层文件,关闭它,向其中写入数据了等。然后解决方案将归结为在 chan close 之后立即执行 chan create 并在跟踪过程中跟踪写入。为了响应 write 调用,您的跟踪例程仍需要 return 丢弃一些字节。


另请阅读 chan and refchan 手册页。