编辑:为清晰起见,对原始示例和替代解决方案框架进行了修改。
行缓冲行为可能与Tcl 8.6中预期的行为有所不同。以下代码块没有任何输出,除非未注释“ chan close”行:
set data {one two four}
set stream [open |[list cat -n] r+]
chan configure $stream -buffering line
chan puts $stream "$data\n"
chan puts $stream "\n"
chan flush $stream
#chan close $stream write
set out [chan read $stream]
puts "output: $out"
chan close $stream
因此,这种简单的解决方案不适用于交互式I / O,这可能与管道两端的同步问题有关。
使用渠道事件结构(例如,基于http://www.beedub.com/book/2nd/event.doc.html)似乎更可取:
proc chanReader { pipe } {
global extState
while 1 {
set len [chan gets $pipe line]
if { $len > 0 } {
puts "<< $line."
continue
} else {
if { [chan blocked $pipe] } {
set extState 1
return
} elseif { [chan eof $pipe] } {
set extState 2
return
}
}
}
}
set data {one two foure}
set timeout 5000
#set stream [open [list | cat -n] r+]
#set stream [open [list | ispell -a] r+]
set stream [open [list | tr a-z A-Z] r+]
#set stream [open [list | fmt -] r+]
chan configure $stream -blocking 0 -buffering line
set extState 0
chan event $stream readable [list chanReader $stream]
foreach word $data {
puts "> $word\n"
chan puts $stream "$word\n"
chan flush $stream
#chan close $stream write
set aID [after $timeout {set extState 3}]
vwait extState
if { $extState == 1 } {
# Got regular output.
after cancel $aID
puts "Cancel $aID."
continue
} elseif { $extState == 2 } {
puts "External program closed."
chan close $stream
exit 2
} elseif { $extState == 3 } {
puts "Timeout."
chan close $stream
exit 3
}
}
puts "End of task."
chan close $stream
exit 0
此代码段可与“ cat -n”和“ ispell -a”外部程序(注释行)一起使用,但对于其他外部程序仍然无效。例如,它不适用于上面的“ tr a-z A-Z”和“ fmt”示例。
如果上面的“ chan close $ stream write”行未注释,我们将收到外部程序的输出,但这会终止与它的交互。如何可靠地(交互地)连接到这些外部程序?
答案 0 :(得分:0)
我猜这里的核心问题是存在两个缓冲源,而Tcl仅能控制其中一个。但是两者都源于以下事实:几乎所有的输出在不到达“交互式”目的地(即终端)时都会被缓冲。 C标准库中基本上有一个调用来确定此问题并启用缓冲功能,并且Tcl也遵循该规则(尽管使用其完全独立的I / O库)。 大量可以加快非交互式流水线处理的速度,但是这意味着,如果您希望在程序认为正在编写时恰好看到每个字节的输出,失望。
当然,程序可以根据需要关闭此缓冲。在Tcl中,这是通过fconfigure $channel -buffering none
(或line
用于行缓冲)来完成的。在cat
中,-n
选项使其等效(在C中调用setvbuf()
),而ispell
可能也是如此。但是大多数程序不是。有些人则不时打电话给fflush()
;这也可行,但也是少数派的做法。因此,使用您正在使用的双向管道,您可以很容易地迫使从Tcl馈入其中的那一侧没有缓冲,但是您通常通常无法让另一侧做同样的事情。
有一个解决方法:使用Expect运行子进程。这就在Tcl和子进程(而不是管道)之间放置了一个伪终端,并欺骗它以为它是在直接与用户对话。但是这样做的结果是,您必须实质性地重写您的Tcl程序,并且您会依赖(非常好!)外部程序包。