Tcl / Expect 由名称管道 blocks/buffers 驱动的脚本意外输出
Tcl / Expect script driven by name pipe blocks/buffers output unexpectedly
我正在尝试编写一个 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 command: ls/pwd" 行,但是生成的 bash shell 没有输出(没有文件列表和当前目录)。现在,再试一次:
echo ls >> ${CMDPIPE}
前两个命令的输出突然出现,但第三个命令(第二个 ls)不可见。继续走,你会注意到显示的输出中有一个 "lag" 似乎是 "buffered" 然后稍后立即转储。
为什么会发生这种情况,我该如何解决?
根据fifo(7):
Normally, opening the FIFO blocks until the other end is opened also.
因此,在进程 read_command
中,它在 set cpipe [open "$::env(CMDPIPE)" r]
上阻塞,并且在您再次 echo ... >> ${CMDPIPE}
之前没有机会显示派生进程的输出。
要解决这个问题,您可以在非阻塞模式下打开 FIFO(命名管道):
set cpipe [open "$::env(CMDPIPE)" {RDONLY NONBLOCK} ]
这个在fifo(7)中也有提到:
A process can open a FIFO in nonblocking mode. In this case, opening for read-only will succeed even if no one has opened on the write side yet ...
以下是您代码的简化版本,对我来说效果很好(在 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
我正在尝试编写一个 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 command: ls/pwd" 行,但是生成的 bash shell 没有输出(没有文件列表和当前目录)。现在,再试一次:
echo ls >> ${CMDPIPE}
前两个命令的输出突然出现,但第三个命令(第二个 ls)不可见。继续走,你会注意到显示的输出中有一个 "lag" 似乎是 "buffered" 然后稍后立即转储。
为什么会发生这种情况,我该如何解决?
根据fifo(7):
Normally, opening the FIFO blocks until the other end is opened also.
因此,在进程 read_command
中,它在 set cpipe [open "$::env(CMDPIPE)" r]
上阻塞,并且在您再次 echo ... >> ${CMDPIPE}
之前没有机会显示派生进程的输出。
要解决这个问题,您可以在非阻塞模式下打开 FIFO(命名管道):
set cpipe [open "$::env(CMDPIPE)" {RDONLY NONBLOCK} ]
这个在fifo(7)中也有提到:
A process can open a FIFO in nonblocking mode. In this case, opening for read-only will succeed even if no one has opened on the write side yet ...
以下是您代码的简化版本,对我来说效果很好(在 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