我不确定这是否荒谬。是不是trace
可以在Tcl中读写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
的手册页,如果没有为channelId
命令指定puts
,则默认为stdout
,即使puts hai
也是如此,stdout
将被访问。对 ? (虽然即使参数为stdout
或stderr
)
答案 0 :(得分:1)
您尝试的解决方案存在的问题是,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 create
之后立即执行chan close
并在跟踪过程中跟踪写入。在响应write
调用时,您的跟踪例程仍然需要返回丢弃的字节数。