“混乱的输出”是与终端控制序列混合的输出,最常见的是ANSI escape codes。
对于依赖 Curses 进行终端输出的二进制文件,在未设置 TERM 环境变量或设置为空字符串的情况下运行命令会导致命令失败。例如,
$ env TERM= htop
Error opening terminal: unknown.
这是因为这些标准库检查 TERM 环境变量,以在终端数据库中找到相应的条目。 (tput 实用程序也使用该数据库,因此您可以使用tput clear 清除终端,tput reset 将终端重置为默认状态(如果终端模式出现乱码很有用),等等。)
要查明command 是否使用 ncurses/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,我们甚至不能使用哑终端(在运行命令之前将TERM设置为哑终端,即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 列出您机器上的所有 terminfo 文件。我的有超过 2700 个。)
对于这类问题有一个独特的、“适当的”解决方案,但这并不容易。
您可以使用适当的伪终端接口pty,特别是通过posix_openpt()、grantpt()、unlockpt() 和ptsname() 的UNIX98 伪终端,而不是只使用管道到子进程。然而,主控端——你的进程——必须像真正的终端一样运行,并处理它所支持的所有控制序列(通过将 TERM 环境变量设置为终端类型)。
您可以在自己的项目中重复使用一些终端仿真器库和代码;我立即想到的是VTE(它使用GTK+ GUI 小部件进行终端显示)和xterm 本身; xterm 源代码中的ctlseqs.txt 是xterm 变体中使用的实际序列的极好列表。另一个有用的项目是GNU screen。
本质上,您的程序然后成为命令使用的终端,并且可以为命令提供输入,并在终端显示上执行命令想要执行的所有更改。您需要做的就是以某种方式记录这些更改。 (这通常称为 scraping,但作为伪终端主机,您实际上只需决定您希望如何存储终端内容:作为类似电影的播放、某种尺寸的单个屏幕或其他方式。)
总的来说,我想说最好的选择是使用TERM=xterm 或变体,或者可能是TERM=ansi,然后过滤掉或替换部分/大部分/所有转义序列以获得您想要的输出。如果列大于 1(最左边的列),则将“移动光标到”命令替换为换行符,后跟适当数量的空格;以及带有一个或多个换行符的“清除屏幕”。它并不完美,但它应该可以作为一个简单的状态机(逐个字符读取终端输出,并使用标准 函数发出过滤后的输出),并且可以很好地满足大多数用途。