在 Tcl 中跟踪标准输出和标准错误
Tracing stdout and stderr in Tcl
我不确定这是否荒谬。 trace
stdout
和 stderr
的读写在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
将被访问。正确的 ? (尽管即使参数为 stdout
或 stderr
也不起作用)
您尝试的解决方案的问题是 stdout
、stderr
和 stdin
不是变量,而是所谓的 "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 丢弃一些字节。
我不确定这是否荒谬。 trace
stdout
和 stderr
的读写在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
将被访问。正确的 ? (尽管即使参数为 stdout
或 stderr
也不起作用)
您尝试的解决方案的问题是 stdout
、stderr
和 stdin
不是变量,而是所谓的 "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 丢弃一些字节。