Dan*_*552 2 ruby io subprocess pty
我想要实现的目标:
通过传递不同的 IO 管道可以捕获 STDOUT/STDERR,但是子进程可以检测到它不在tty. 例如git log,不会打印影响文本颜色的字符,也不会使用其寻呼机。
使用 apty启动进程本质上是“欺骗”子进程,使其认为它是由用户启动的。据我所知,这正是我想要的,而且其结果基本上满足了所有要求。
我测试解决方案是否满足我的需求的一般测试是:
ls -al正常吗?vim正常吗?irb正常吗?以下 Ruby 代码可以检查以上所有内容:
to_execute = "vim"
output = ""
require 'pty'
require 'io/console'
master, slave = PTY.open
slave.raw!
pid = ::Process.spawn(to_execute, :in => STDIN, [:out, :err] => slave)
slave.close
master.winsize = $stdout.winsize
Signal.trap(:WINCH) { master.winsize = $stdout.winsize }
Signal.trap(:SIGINT) { ::Process.kill("INT", pid) }
master.each_char do |char|
STDOUT.print char
output.concat(char)
end
::Process.wait(pid)
master.close
Run Code Online (Sandbox Code Playgroud)
这在大多数情况下都有效,但事实证明它并不完美。由于某种原因,某些应用程序似乎无法切换到某种raw状态。尽管vim工作得很好,但事实证明 Neovim 却没有。起初我以为这是 neovim 中的一个错误,但后来我能够使用 Rust 语言的 Termion crate 重现该问题。
通过在执行前手动设置为 raw ( IO.console.raw!),neovim 等应用程序的行为符合预期,但类似的应用程序则irb不然。
奇怪的是,在这个 pty 中,在 Python 中生成了另一个pty,允许应用程序按预期工作(使用python -c 'import pty; pty.spawn("/usr/local/bin/nvim")')。这显然不是一个真正的解决方案,但仍然很有趣。
对于我的实际问题,我想我正在寻求任何帮助来解决这个奇怪的raw问题,或者说,如果我完全误解了 tty/pty,我应该在哪里/如何看待问题有任何不同的方向。
[编辑:请参阅底部的修订更新]
弄清楚了 :)
为了真正理解这个问题,我阅读了大量有关 PTY 工作原理的文章。在我把它画出来之前,我认为我并没有真正理解它。基本上 PTY 可用于终端模拟器,这是考虑其数据流的最简单方法:
keyboard -> OS -> terminal -> master pty -> termios -> slave pty -> shell
|
v
monitor <- OS <- terminal <- master pty <- termios
Run Code Online (Sandbox Code Playgroud)
(注意:这可能不是100%正确,我绝对不是这个主题的专家,只是发布它,以防它帮助其他人理解它)
因此,图中我没有真正意识到的重要一点是,当您键入时,您在屏幕上看到输入的唯一原因是因为它被传回(向左)到母版。
首先,这个 ruby 脚本应该首先将 tty 设置为 raw ( IO.console.raw!),它可以在执行完成后恢复它 ( IO.console.cooked!)。这将确保此父 Ruby 脚本不会打印键盘输入。
第二件事是从属本身不应该是原始的,因此该slave.raw!调用被删除。为了解释这一点,我最初添加了这个,因为它从输出中删除了额外的回车符:运行echo hello结果为"hello\r\n". 我错过的是这个回车是终端模拟器的关键指令(哎呀)。
第三件事,该进程应该只与从站交谈。通过STDIN感觉很方便,但它打乱了图中所示的流程。
这就带来了如何传递用户输入的新问题,所以我尝试了这个。所以我们基本上传递STDIN给master:
input_thread = Thread.new do
STDIN.each_char do |char|
master.putc(char) rescue nil
end
end
Run Code Online (Sandbox Code Playgroud)
这种方法有效,但它有自己的问题,因为某些交互过程有时没有收到密钥。时间会证明一切,但使用它IO.copy_stream似乎可以解决这个问题(当然读起来更好)。
input_thread = Thread.new { IO.copy_stream(STDIN, master) }
Run Code Online (Sandbox Code Playgroud)
8月21日更新:
因此,上面的示例大部分都有效,但由于某些原因,像 CTRL+c 这样的键仍然无法正常运行。我什至查阅了其他人的方法,看看我可能做错了什么,实际上,这似乎是相同的方法 - 就像IO.copy_stream(STDIN, master)成功发送3给主人一样。以下似乎没有任何帮助:
master.putc 3
master.putc "\x03"
master.putc "\003"
Run Code Online (Sandbox Code Playgroud)
在我深入研究尝试用较低级别的语言实现这一目标之前,我又尝试了一种东西 - 块语法。显然,块语法神奇地解决了这个问题。
为了防止这个答案变得过于冗长,以下方法似乎有效:
keyboard -> OS -> terminal -> master pty -> termios -> slave pty -> shell
|
v
monitor <- OS <- terminal <- master pty <- termios
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1302 次 |
| 最近记录: |