我不确定这是否荒谬。在Tcl中trace
stdout
和stderr
的读写是不是可以?
我尝试了以下方法,但没有发现任何线索。
% 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
这意味着即使使用 puts hai
,stdout
也会被访问。右?(尽管即使参数为stdout
或stderr
,它也不起作用(
您尝试的解决方案的问题在于stdout
、stderr
和stdin
不是变量,而是所谓的"通道"的名称。从本质上讲,它们位于一个单独的命名空间中(而不是由 namespace
Tcl 命令操作的命名空间!(:您可以使用 chan names
命令获取它们的列表,但你不能rename
一个通道,或者为它分配一个值或unset
它:这些操作对通道根本没有意义,而是会影响该名称的变量。
跟踪通道的一种方法是使用另一个"脚本级"通道和"代理"所有操作实际破坏它。 这个技巧使用了一个鲜为人知的Tcl功能:当你关闭Tcl的一个标准通道并立即打开一个通道(无论是"真实"还是"脚本级"(时,该通道将被注册代替刚刚关闭的标准通道。 因此,如果我们关闭一个标准通道并立即在其位置创建我们自己的"代理"通道,我们就会颠覆该标准通道。
这些"脚本级"("反射"(通道需要 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,您将可以访问底层文件描述符/句柄(因此不需要 /proc/self/fd/...
(和包装它的 Tcl 对象(因此您可以立即克隆它(并知道底层通道写入了多少字节。
哦,请注意,如果您可以将脚本发送的数据扔到颠覆的标准通道中,请不要重新打开真正的底层文件、关闭它、向其中写入数据等。 然后,解决方案将归结为在跟踪过程中chan close
和跟踪写入后立即执行chan create
。 为了响应write
调用,您的跟踪例程仍需要返回丢弃的字节数。
另请阅读chan
和refchan
手册页。