Скрипт Tcl / Expect, управляемый блоками / буферами именных каналов, неожиданно выводится - PullRequest
0 голосов
/ 19 ноября 2018

Я пытаюсь написать сценарий ожидания, который реагирует на ввод данных при чтении канала. Рассмотрите этот пример в файле "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}

В первом терминале строки "Got command: ls / pwd" печатаются сразу же, как только вы нажимаете enter для каждой команды echo, но нет вывода из порожденной оболочки bash (без списка файлов и текущего каталога). Теперь попробуйте еще раз:

echo ls >> ${CMDPIPE}

Внезапно появляется вывод из первых двух команд, но третья команда (вторая ls) не видна. Продолжайте, и вы заметите, что в отображаемом выводе есть «лаг», который кажется «буферизованным», а затем сразу же сбрасывается.

Почему это происходит и как я могу это исправить?

1 Ответ

0 голосов
/ 20 ноября 2018

Согласно fifo (7) :

Как правило, открытие блоков FIFO до открытия другого конца также.

Итак,в процедуре read_command он блокируется на set cpipe [open "$::env(CMDPIPE)" r] и не получает возможности отображать вывод порожденного процесса, пока вы снова не echo ... >> ${CMDPIPE}.

Чтобы обойти это, вы можете открыть FIFO (с именемpipe) в неблокирующем режиме:

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

...