【问题标题】:How to detect if a command is interactive or not in linux如何在linux中检测命令是否是交互式的
【发布时间】:2021-12-12 20:16:12
【问题描述】:

我想保存特定子流程的输出以供以后使用。为此,我使用 tee 将输出显示到标准输出和日志文件。但是,当涉及到诸如 ncdu 或 htop 之类的交互式命令时,它当然无法将其正确写入文件。因此,我希望能够在运行命令之前(我认为这是不可能的)或在读取此类命令的混乱日志文件时知道命令是否是交互式的。我会假设一个交互式程序写入标准输出的东西是普通命令不会写入的,这将使我能够区分两者。

【问题讨论】:

  • 在 C 中,你有 isatty 函数。在交互模式下,1 应该是一个 tty。
  • I would assume that there is something that an interactive program writes to stdout that a normal command wouldn't 嗯,没有。所以不,你不能。您可以建立一个程序和选项列表,您希望在其中应用您的过滤。无论如何,你在问 XY 问题,因为在我看来你想使用 script 来记录交互式 sessions
  • grep ncurses binaryfile >/dev/null && echo NCURSES || echo no ncurses
  • @mmeisson 这是一个不同的问题。他想知道他从脚本运行的命令是否是交互式的(全屏),而不是脚本是否正在交互式运行。

标签: c linux unix tee


【解决方案1】:

“混乱的输出”是与终端控制序列混合的输出,最常见的是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=vanillaCOLUMNS=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(最左边的列),则将“移动光标到”命令替换为换行符,后跟适当数量的空格;以及带有一个或多个换行符的“清除屏幕”。它并不完美,但它应该可以作为一个简单的状态机(逐个字符读取终端输出,并使用标准 函数发出过滤后的输出),并且可以很好地满足大多数用途。

【讨论】:

  • 感谢您的广泛回答!虽然我认为操纵 ENV 变量以过滤掉转义字符可能有点棘手,而且我不知道如果我希望其他人使用这个 shell 并且它会摆弄他们的环境变量并且他们甚至没有注意到它是否具有便携性