我正在尝试编写一个记录shell,例如捕获以结构化格式运行的命令数据。为此,我使用
到目前为止还不错。然而,在这个记录shell中运行像
我认为我需要做的是将pty设置为raw模式。为此,我尝试了以下操作:
例如,我们获取父终端的属性,去除我认为对应于将tty设置为原始模式的各种标志(基于this code和
然后,我使用
这肯定会改变终端设置(使用
编辑:完整可运行代码可以在https://github.com/nc6/tabula找到——我为了这篇文章简化了一些内容。
readline
读取命令,然后在子shell中执行它们,同时捕获诸如花费时间、环境、退出状态等内容。到目前为止还不错。然而,在这个记录shell中运行像
vi
或less
之类的初始尝试失败了。调查表明,需要做的是建立一个伪终端并将子shell连接到其中,而不是普通管道。这样可以防止vi抱怨没有连接到终端,但仍然失败了——我会在屏幕上打印一些无意义的东西,并且命令会作为编辑器中的字符打印出来,例如“ESC”只会显示^[
。我认为我需要做的是将pty设置为raw模式。为此,我尝试了以下操作:
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')
例如,我们获取父终端的属性,去除我认为对应于将tty设置为原始模式的各种标志(基于this code和
System.Posix.Terminal
的哈达克),然后在pty的两侧设置这些标志。然后,我使用
createProcess
在shell中启动一个进程,并使用waitForProcess
连接到它,为子进程的stdin和stdout句柄提供pty的从属端。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
这肯定会改变终端设置(使用
stty
确认),但并不能解决问题。我是不是漏掉了什么?还有其他设备需要设置属性吗?编辑:完整可运行代码可以在https://github.com/nc6/tabula找到——我为了这篇文章简化了一些内容。