Tcl /由名称管道块/缓冲区驱动的期望脚本意外输出

时间:2018-11-19 19:30:28

标签: tcl expect

我正在尝试编写一个期望脚本,该脚本对读取管道的输入做出反应。考虑文件“ contoller.sh”中的此示例:

#!/usr/bin/env expect

spawn bash --noprofile --norc

set timeout 3
set success 0
send "PS1='Prompt: '\r"
expect {
  "Prompt: " { set success 1 }
}
if { $success != 1 } { exit 1 }

proc do { cmd } {
  puts "Got command: $cmd"
  set success 0
  set timeout 3
  send "$cmd\r"
  expect {
    "Prompt: " { set success 1 }
  }
  if { $success != 1 } { puts "oops" }
}

set cpipe [open "$::env(CMDPIPE)" r]
fconfigure $cpipe -blocking 0
proc read_command {} {
  global cpipe
  if {[gets $cpipe cmd] < 0} {
    close $cpipe
    set cpipe [open "$::env(CMDPIPE)" r]
    fconfigure $cpipe -blocking 0
    fileevent $cpipe readable read_command
  } else {
    if { $cmd == "exit" } {
      exp_close
      exp_wait
      exit 0
    } elseif { $cmd == "ls" } {
      do ls
    } elseif { $cmd == "pwd" } {
      do pwd
    }
  }
}

fileevent $cpipe readable read_command
vwait forever;

假设您这样做:

export CMDPIPE=~/.cmdpipe
mkfifo $CMDPIPE
./controller.sh

现在,从另一个终端尝试:

export CMDPIPE=~/.cmdpipe
echo ls >> ${CMDPIPE}
echo pwd >> ${CMDPIPE}

在第一个终端中,只要在每个echo命令上按Enter键,就会立即打印“ Got命令:ls / pwd”行,但是生成的bash shell没有输出(没有文件列表和当前目录)。现在,再试一次:

echo ls >> ${CMDPIPE}

突然出现前两个命令的输出,但第三个命令(第二个ls)不可见。继续前进,您会发现显示的输出中存在“滞后”,似乎已“缓冲”,然后立即转储。

为什么会这样,我该如何解决?

1 个答案:

答案 0 :(得分:2)

根据fifo(7)

  

通常,打开FIFO块,直到另一端也打开。

因此,在过程read_command中,它阻塞在set cpipe [open "$::env(CMDPIPE)" r]上,直到再次echo ... >> ${CMDPIPE}时才有机会显示生成的进程的输出。

要解决此问题,可以在非阻塞模式下打开FIFO(命名管道):

set cpipe [open "$::env(CMDPIPE)" {RDONLY NONBLOCK} ]

fifo(7)中也提到了这一点:

  

一个进程可以在非阻塞模式下打开FIFO。在这种情况下,即使尚未在写端打开任何人,只读打开操作也会成功...

以下是代码的简化版本,对我来说很好用(在 Debian 9.6 上进行了测试)。

spawn bash --norc
set timeout -1

expect -re {bash-[.0-9]+[#$] $}
send "PS1='P''rompt: '\r"
#         ^^^^
expect "Prompt: "

proc do { cmd } {
    send "$cmd\r"
    if { $cmd == "exit" } {
        expect eof
        exit
    } else {
        expect "Prompt: "
    }
}

proc read_command {} {
    global cpipe
    if {[gets $cpipe cmd] < 0} {
        close $cpipe
        set cpipe [open cpipe {RDONLY NONBLOCK} ]
        fileevent $cpipe readable read_command
    } else {
        do $cmd
    }
}

set cpipe [open cpipe {RDONLY NONBLOCK} ]
fileevent $cpipe readable read_command
vwait forever

enter image description here