Почему библиотека PTY ruby ​​не может захватить ввод, когда оболочка имеет подпроцессы? - PullRequest
2 голосов
/ 27 сентября 2010

Я пишу эмулятор терминала в ruby, используя библиотеку PTY. /dev/tty0 - это файл устройства, подключенный к клавиатуре. Я порождаю раковину так:

shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i < /dev/tty0'

В основном это работает, но когда подпрограмма запускается в оболочке, shell[0] не выводит ввод с клавиатуры на этот подпроцесс. Например: когда я отправляю "cat\nasdf" через shell[1], "cat" возвращается через shell[0], а "asdf" - нет. Почему это происходит, и как я могу это исправить?

Редактировать
Вот мой код ChumbyScreen - это внешний модуль, управляющий экраном встроенного устройства, для которого я пишу (он называется «Чамби»). Метод write выводит символ на экран.

require 'pty'

def handle_escape(io)
  actions = 'ABCDEFGHJKSTfmnsulh'
  str, action = '', nil
  loop do
    c = io.read(1)
    if actions.include? c
      action = c
      break
    else
      str += c
    end
  end
  case action
  when 'J'
    ChumbyScreen.x = 0
  end
end

system '[ -e /dev/tty0 ] || mknod /dev/tty0 c 4 0'
shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i < /dev/tty0'

loop do
  c = shell[0].read(1)
  if c == "\e"
    c2 = shell[0].read(1)
    if c2 == '['
      handle_escape shell[0]
      next
    else
      c += c2
    end
  end
  ChumbyScreen.write c
end

Прочитав ответ Шоданекса, я попробовал это:

require 'pty'

def handle_escape(io)
  actions = 'ABCDEFGHJKSTfmnsulh'
  str, action = '', nil
  loop do
    c = io.read(1)
    if actions.include? c
      action = c
      break
    else
      str += c
    end
  end
  case action
  when 'J'
    ChumbyScreen.x = 0
  end
end

system '[ -e /dev/tty0 ] || mknod /dev/tty0 c 4 0'
shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i'

Thread.new do
  k = open '/dev/tty0', File::RDONLY
  loop do
    shell[1].write k.read(1)
  end
end.priority = 1

loop do
  c = shell[0].read(1)
  if c == "\e"
    c2 = shell[0].read(1)
    if c2 == '['
      handle_escape shell[0]
      next
    else
      c += c2
    end
  end
  ChumbyScreen.write c
end

Это работает, но введенные символы не отображаются, пока я не нажму клавишу ввода. Это должно быть как-то в буфере строки - как мне пройти через это? Также Control-C и Control-D ничего не делают. Мне нужно, чтобы они отправили eof и завершили процесс.

1 Ответ

2 голосов
/ 27 сентября 2010

В режиме ввода tty по умолчанию используется линейный ввод, поэтому вы ничего не увидите, пока не выведете перевод строки.

Я предлагаю использовать strace для отладки такого поведения. Таким образом, вы можете увидеть системный вызов и, например, посмотреть, заблокированы ли вы при чтении, ожидая большего ввода и т. Д.

Когда вы не используете '</ dev / tty0', это работает, верно? По сути, вы хотите повторить характер. Если вы делаете следующее: </p>

shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i'
shell[1].puts("cat\nasdf")
s = shell[0].read(16)
puts s

И вы выполняете процесс, используя:

strace -ff -o test.log -e trace=read,write ./testr.rb

В выводе вы увидите asdf дважды. Но если вы посмотрите на код strace, то увидите, что подпроцесс cat записывает asdf только один раз, а его родительский процесс, то есть оболочка, никогда не записывает asdf.

Так почему же есть два вывода asdf? Потому что слой tty делает локальное эхо. Так что, когда вы вводите что-то в shell, оно отправляется на pty и драйвер pty:

  • Запишите это на рабскую сторону псевдотерминала
  • Отдает его на главную сторону.

Так что же происходит, когда вы делаете sh -i </dev/tty0? Символы, поступающие с клавиатуры, выводятся в / dev / tty0, а не на вывод оболочки.

Оболочка не генерирует эхо, слой tty есть, поэтому вы хотите выполнить следующее (примите это за псевдокод, я не компетентен в ruby):

# Emulate terminal behavior with pty
shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i'
keyboard = open(/dev/tty0)

input = keyboard.read()
shell[1].write(input)
puts shell[0].read(...)

Теперь, если вам нужно что-то интерактивное, вам нужно будет настроить / dev / tty0 в необработанном режиме и использовать команду select, чтобы узнать, когда вы можете читать без блокировки и когда есть данные, доступные для вывода.

Для настройки tty в режиме raw вы можете попробовать использовать

stty -F /dev/tty0 -cooked
...