我正在尝试编写一个期望脚本,该脚本对读取管道的输入做出反应。考虑文件“ 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)不可见。继续前进,您会发现显示的输出中存在“滞后”,似乎已“缓冲”,然后立即转储。
为什么会这样,我该如何解决?
答案 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