我想将输出保存为特定的子流程以供以后使用。为此,我使用tee向stdout和日志文件显示输出。然而,当涉及到像ncdu或htop之类的交互式命令时,它当然不能正确地将其写入文件。因此,我想在运行命令之前(我认为这是不可能的)或在读取此类命令的混乱日志文件时,能够知道命令是否是交互式的。我假设交互程序向stdout写入的内容是普通命令无法写入的,这将使我能够区分两者。
;混乱的输出";是与终端控制序列混合的输出,最常见的是ANSI转义码。
对于依赖Curses进行终端输出的二进制文件,在TERM环境变量未设置或设置为空字符串的情况下运行命令会导致命令失败。例如,
$ env TERM= htop
Error opening terminal: unknown.
这是因为这些标准库检查TERM环境变量,以便在终端数据库中找到相应的条目。(tput实用程序也使用该数据库,因此您可以使用tput clear
清除终端,使用tput reset
将终端重置为默认状态(如果有什么东西扰乱了终端模式,则很有用),依此类推。)
要了解command
是使用ncurse/curses还是终端信息数据库编译的(terminfo支持,您可以运行例如ldd command | grep -qe 'libn*curses' -e 'libtinfo' && echo Yes || echo No
。然而,这只是告诉这些命令是否正确地支持不同的终端,而不是它们是否需要终端工作。
还有一些脚本和工具,如ls --color
,它们不使用curses或terminfo支持,而是直接发出ANSI控制序列。(在ls
的情况下,使用LS_COLORS
环境变量中定义的序列,但仅当输出到终端时使用。正如mmeisson在对原始问题的评论中所提到的,使用例如isatty(STDOUT_FILENO)进行检测是微不足道的。)
在htop
的情况下,我们甚至不能使用dumb终端(在运行命令之前,将TERM设置为dumb,即env TERM=dumb htop </dev/null &>output
),因为htop将任务列表呈现为所有空格!很烦人。至少有了top
(env TERM=dump top </dev/null &>output
),就可以得到漂亮的、无修饰的ASCII输出。
香草终端可能就足够了。在执行子进程或命令之前,请设置TERM=vanilla
和COLUMNS=80
,例如env TERM=vanilla COLUMNS=80 htop </dev/null &>output
。然而,尽管您现在从htop
获得了更多的输出,但换行符仍然缺失(因为htop只使用光标移动,而vanilla终端没有这些)。
可能还有另一个适用于您的终端,例如,其中生成的所有转义序列都很容易检测和过滤,但足够复杂,例如htop输出是完整的。不过我不知道。您可以创建一个,并将其添加到terminfo数据库之一(例如/etc/terminfo/e/easy
)。(你可以使用ls -1 {/etc,/lib,/usr/share}/terminfo/?/* | sed -e 's|^.*/||g' | sort
列出你的机器拥有的所有终端文件。我的有2700多个。)
有一个明显的";适当的";这类问题的解决方案,但并不容易。
您不需要仅使用子流程的管道,而是通过posix_openpt()、grantpt()、unlockpt()和ptsname()使用适当的伪终端接口pty,特别是UNIX98伪终端。然而,主端——您的进程——必须表现得像一个真正的终端,并处理它所支持的所有控制序列(通过将TERM环境变量设置为终端类型)。
有一些终端模拟器库和代码,您可以在自己的项目中重用;我立刻想到的是VTE(它使用GTK+GUI小部件作为终端显示)和xterm本身;xterm源中的CCD_ 18是xterm变体中使用的实际序列的优秀列表。另一个有用的项目是GNU屏幕。
从本质上讲,您的程序将成为命令使用的终端,并且可以为命令提供输入,并在终端显示器上执行命令想要执行的所有更改。你所需要做的就是以某种方式记录这些变化。(这通常被称为抓取,但作为伪终端主机,您实际上只是决定如何存储终端内容:作为电影般的播放、某种大小的单个屏幕或其他方式。)
总的来说,我认为最好的选择是使用TERM=xterm
或变体,或者TERM=ansi
,然后过滤掉或替换一些/大部分/所有转义序列,以获得您想要的输出。将";将光标移动到";命令到换行符,如果列大于1(最左边的列),后面跟着适当数量的空格;以及";透明屏幕";带有一个或多个换行符。它并不完美,但作为一个简单的状态机(使用标准<stdio.h>函数逐字符读取终端输出,并发出过滤后的输出)应该是可行的,并且对于大多数目的来说都足够好。