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

标签 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] 不会将键盘输入输出到该子进程。例如:当我通过 shell[1] 发送 "cat\nasdf" 时,"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

它可以工作,但我输入的字符在我按下 enter 之前不会显示。它必须以某种方式进行行缓冲 - 我该如何解决这个问题? Control-C 和 Control-D 也什么都不做。我需要他们发送一个 eof 并终止一个进程。

最佳答案

tty输入方式默认是线路输入,所以你什么都看不到,直到你输出 一个换行符。

我建议使用 strace调试这种行为。这样您就可以看到系统调用,例如查看您是否在等待更多输入等的读取时被阻塞。

当你不使用 '

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 这个过程:

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

在输出中,您将看到两次 asdf。 但是如果你看一下strace代码,你会发现cat子进程只写了一次asdf,而它的父进程,即shell,从来没有写过asdf。

那么为什么会有两个'asdf'输出?因为tty层是在做local echo。因此,当您在 shell 中键入内容时,它会转到 pty,而 pty 驱动程序:

  • 写到伪tty的slave端
  • 将它回显到 master 端。

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

shell 没有做任何回显,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,你可以尝试使用

stty -F /dev/tty0 -cooked

关于ruby - 当 shell 有子进程时,为什么 ruby​​ 的 PTY 库无法捕获输入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3800471/

相关文章:

javascript - Ruby on Rails -> 动态创建 div

linux - epoll会通知监听同一个fd的所有进程吗?

python - 从文件输入 numpy 数组

ruby - 在 Ruby 中检索文件

ruby - Ruby 中有点复杂的 eval

ruby - 是否可以使用 watir 选择或突出显示文本框中的文本子字符串?

python - Beautiful Soup for Ruby 最接近的等价物是什么?

linux - 在 BASH 中构建简单的计算器

linux - 显示匹配模式的 tail 命令

ruby-on-rails - 在 bash 中执行 rake 命令