从haskell程序中运行vi(处理ptys)

时间:2022-02-15 21:02:00

I'm trying to write a logging shell; e.g. one that captures data about commands that are being run in a structured format. To do this, I'm using readline to read in commands and then executing them in a subshell whilst capturing things such as the time taken, the environment, the exit status and so on.

我正在尝试编写一个日志记录shell;例如一个捕获有关以结构化格式运行的命令的数据。为此,我使用readline读入命令,然后在子shell中执行它们,同时捕获诸如所花费的时间,环境,退出状态等内容。

So far so good. However, initial attempts to run things such as vi or less from within this logging shell failed. Investigation suggested that the thing to do was to establish a pseudo-tty and connect the subshell to that rather than to a normal pipe. This stops vi complaining about not being connected to a terminal, but still fails - I get some nonsense printed to the screen and commands print as characters in the editor - e.g. 'ESC' just displays ^[.

到现在为止还挺好。但是,从此日志记录shell中运行诸如vi或更少内容的初始尝试失败。调查表明要做的事情是建立一个伪tty并将子shell连接到那个而不是普通管道。这会阻止vi抱怨没有连接到终端,但仍然失败 - 我在屏幕上打印了一些废话并且命令在编辑器中作为字符打印 - 例如'ESC'只显示^ [。

I thought that what I needed to do was put the pty in raw mode. To do this, I tried the following:

我认为我需要做的是将pty置于原始模式。为此,我尝试了以下方法:

  pty <- do
    parentTerminal <- getControllingTerminalName >>= 
                      \a -> openFd a ReadWrite Nothing defaultFileFlags
    sttyp <- getTerminalAttributes parentTerminal
    (a, b) <- openPseudoTerminal
    let rawModes = [ProcessInput, KeyboardInterrupts, ExtendedFunctions, 
                    EnableEcho, InterruptOnBreak, MapCRtoLF, IgnoreBreak, 
                    IgnoreCR, MapLFtoCR, CheckParity, StripHighBit, 
                    StartStopOutput, MarkParityErrors, ProcessOutput]
        sttym = withoutModes rawModes sttyp
        withoutModes modes tty = foldl withoutMode tty modes
    setTerminalAttributes b sttym Immediately
    setTerminalAttributes a sttym Immediately
    a' <- fdToHandle a
    b' <- fdToHandle b
    return (a',b')

E.g. we get the parent terminal's attributes, remove the various flags that I think correspond to setting the tty into raw mode (based on this code and the haddock for System.Posix.Terminal), and then set these on both sides of the pty.

例如。我们得到父终端的属性,删除我认为对应于将tty设置为原始模式的各种标志(基于此代码和System.Posix.Terminal的haddock),然后在pty的两侧设置这些。

I then start up a process within a shell using createProcess and use waitForProcess to connect to it, giving the slave side of the pty for the stdin and stdout handles on the child process:

然后我使用createProcess在shell中启动一个进程,并使用waitForProcess连接到它,为子进程的stdin和stdout句柄提供pty的slave端:

eval :: (Handle, Handle) -> String -> IO ()
eval pty command = do
    let (ptym, ptys) = pty
    (_, _, hErr, ph) <- createProcess $ (shell command) { 
          delegate_ctlc = True
        , std_err = CreatePipe
        , std_out = UseHandle ptys
        , std_in = UseHandle ptys
      }
    snipOut <- tee ptym stdout
    snipErr <- sequence $ fmap (\h -> tee h stderr) hErr
    exitCode <- waitForProcess ph
    return ()
  where tee :: Handle -> Handle -> IO B.ByteString
        tee from to = DCB.sourceHandle from
            $= DCB.conduitHandle to -- Sink contents to out Handle
            $$ DCB.take 256 -- Pull off the start of the stream

This definitely changes terminal settings (confirmed with stty), but doesn't fix the problem. Am I missing something? Is there some other device I need to set attributes on?

这肯定会改变终端设置(用stty确认),但不能解决问题。我错过了什么吗?我需要设置一些其他设备来设置属性吗?

Edit: The full runnable code is available at https://github.com/nc6/tabula - I've simplified a few things for this post.

编辑:完整的可运行代码可以在https://github.com/nc6/tabula上找到 - 我简化了这篇文章的一些内容。

2 个解决方案

#1


0  

This is how you create the vi process:

这是您创建vi进程的方法:

(_, _, hErr, ph) <- createProcess $ (shell command) { 

Those return values are stdin/stdout/stderr. You throw away stdin/stdout (and keep stderr). You will need those to communicate with vi. Basically, when you type in ESC, it isn't even getting to the process.

这些返回值是stdin / stdout / stderr。你扔掉stdin / stdout(并保持stderr)。你需要那些与vi沟通。基本上,当您键入ESC时,它甚至没有进入该过程。

As a larger architectural note- You are rewriting not just the terminal code, but a full REPL/shell script.... This is a larger project than you probably want to get into (go read the bash manual to see all the stuff they needed to implement). You might want to consider wrapping around a user choosable shell script (like bash). Unix is pretty modular this way, that is why xterm, ssh, the command prompt etc all work the same way- they proxy the chosen shell script, rather than each write their own.

作为一个更大的架构笔记 - 您不仅要重写终端代码,还要重写完整的REPL / shell脚本....这是一个比您可能想要进入的更大的项目(请阅读bash手册以查看所有内容需要实施)。您可能想要考虑包装用户可选择的shell脚本(如bash)。 Unix非常模块化,这就是为什么xterm,ssh,命令提示符等都以相同的方式工作 - 它们代理所选择的shell脚本,而不是每个都编写自己的脚本。

#2


0  

@jamshidh pointed out that I wasn't actually connecting my stdin to the master side of the pty, so the issues I was getting were nothing to do with vi or terminal modes, and entirely to do with not passing in any input!

@jamshidh指出我实际上并没有把我的标准输入连接到pty的主方面,所以我得到的问题与vi或终端模式无关,完全与不传入任何输入有关!

#1


0  

This is how you create the vi process:

这是您创建vi进程的方法:

(_, _, hErr, ph) <- createProcess $ (shell command) { 

Those return values are stdin/stdout/stderr. You throw away stdin/stdout (and keep stderr). You will need those to communicate with vi. Basically, when you type in ESC, it isn't even getting to the process.

这些返回值是stdin / stdout / stderr。你扔掉stdin / stdout(并保持stderr)。你需要那些与vi沟通。基本上,当您键入ESC时,它甚至没有进入该过程。

As a larger architectural note- You are rewriting not just the terminal code, but a full REPL/shell script.... This is a larger project than you probably want to get into (go read the bash manual to see all the stuff they needed to implement). You might want to consider wrapping around a user choosable shell script (like bash). Unix is pretty modular this way, that is why xterm, ssh, the command prompt etc all work the same way- they proxy the chosen shell script, rather than each write their own.

作为一个更大的架构笔记 - 您不仅要重写终端代码,还要重写完整的REPL / shell脚本....这是一个比您可能想要进入的更大的项目(请阅读bash手册以查看所有内容需要实施)。您可能想要考虑包装用户可选择的shell脚本(如bash)。 Unix非常模块化,这就是为什么xterm,ssh,命令提示符等都以相同的方式工作 - 它们代理所选择的shell脚本,而不是每个都编写自己的脚本。

#2


0  

@jamshidh pointed out that I wasn't actually connecting my stdin to the master side of the pty, so the issues I was getting were nothing to do with vi or terminal modes, and entirely to do with not passing in any input!

@jamshidh指出我实际上并没有把我的标准输入连接到pty的主方面,所以我得到的问题与vi或终端模式无关,完全与不传入任何输入有关!