为什么当shell有子进程时,ruby的PTY库无法捕获输入?

时间:2010-09-27 00:45:51

标签: ruby linux io ipc pty

我正在使用PTY库在ruby中编写终端模拟器。 /dev/tty0是连接到键盘的设备文件。我像这样产生shell:

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

它主要起作用,但是当在shell中启动子进程时,shell[0]不会将键盘输入输出到该子进程。例如:当我通过"cat\nasdf"发送shell[1]时,"cat"会通过shell[0]返回,但"asdf"则不会。为什么会发生这种情况,我该如何解决?

修改的:
这是我的代码。 ChumbyScreen是一个外部模块,用于控制我为此编写的嵌入式设备的屏幕(称为“Chumby”)。 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

在阅读了shodanex的回答后,我尝试了这个:

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 个答案:

答案 0 :(得分:2)

tty输入模式默认为线路输入,因此在输出之前不会看到任何内容 换行。

我建议使用strace来调试此类行为。通过这种方式,您可以看到系统调用,例如,查看是否在等待更多输入的读取时被阻止等等。

当你不使用'&lt; / dev / tty0',它确实有效,对吗? 基本上,你想要的是对人物的回应。如果您执行以下操作:

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,而它的父进程,即shell,从不写asdf。

那么为什么有两个'asdf'输出?因为tty层正在做本地回声。因此,当您在shell中键入内容时,它会转到pty和pty驱动程序:

  • 将其写入伪tty
  • 的从属端
  • 回应主人。

那么当你做sh -i </dev/tty0时会发生什么?来自键盘的字符将回显到/ dev / tty0,而不是回显到shell的输出。

shell没有做任何回声,tty层是,所以你要做的就是下面的(把它作为伪代码,我不能胜任红宝石):

# 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,您可以尝试使用

stty -F /dev/tty0 -cooked