Tcl 8.6中奇怪的行缓冲行为?

时间:2018-12-07 00:14:02

标签: tcl

编辑:为清晰起见,对原始示例和替代解决方案框架进行了修改。

行缓冲行为可能与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”行未注释,我们将收到外部程序的输出,但这会终止与它的交互。如何可靠地(交互地)连接到这些外部程序?

1 个答案:

答案 0 :(得分:0)

我猜这里的核心问题是存在两个缓冲源,而Tcl仅能控制其中一个。但是两者都源于以下事实:几乎所有的输出在不到达“交互式”目的地(即终端)时都会被缓冲。 C标准库中基本上有一个调用来确定此问题并启用缓冲功能,并且Tcl也遵循该规则(尽管使用其完全独立的I / O库)。 大量可以加快非交互式流水线处理的速度,但是这意味着,如果您希望在程序认为正在编写时恰好看到每个字节的输出,失望。

当然,程序可以根据需要关闭此缓冲。在Tcl中,这是通过fconfigure $channel -buffering none(或line用于行缓冲)来完成的。在cat中,-n选项使其等效(在C中调用setvbuf()),而ispell可能也是如此。但是大多数程序不是。有些人则不时打电话给fflush();这也可行,但也是少数派的做法。因此,使用您正在使用的双向管道,您可以很容易地迫使从Tcl馈入其中的那一侧没有缓冲,但是您通常通常无法让另一侧做同样的事情。

有一个解决方法:使用Expect运行子进程。这就在Tcl和子进程(而不是管道)之间放置了一个伪终端,并欺骗它以为它是在直接与用户对话。但是这样做的结果是,您必须实质性地重写您的Tcl程序,并且您会依赖(非常好!)外部程序包。