为什么 bash 交互式 shell 默认情况下会写入其提示符并将其交互式输入回显到 stderr?

为什么 bash 交互式 shell 默认情况下会写入其提示符并将其交互式输入回显到 stderr?

我刚刚注意到 bash 交互式 shell 默认情况下会写入提示符并将您键入的任何内容回显到文件描述符 2 (stderr)。这可以通过在 strace 中运行 bash 来验证。

这是什么原因呢?为什么它不将这些东西写入标准输出?

答案1

可以看到原始 UNIX 版本 5-7 做了同样的事情。 (UFD output = 2; http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/main‌​.c

time是/曾经是一个内置函数,它输出到 stderr 绝对有用。然而time它不是 V5 中的内置函数之一。

我认为没有什么大的原因不是将终端输出写入 stderr。 shell 的输出要么是明显的错误消息,要么是仅用于交互式终端的输出。没有必要通过重定向标准输出来重定向交互式输出。

虽然 stderr 是在 V6 中引入的,而不是 V5 中引入的,但在 V5 中,如果需要,在关闭旧 FD 2 后sh手动将 stdout 发送到 FD 2。dup()似乎他们已经发现需要打印错误消息,例如exec()在尝试启动类似命令时失败foo > output

现在注意如何袖珍的历史上的 UNIX 代码是。它故意保持较短,因为物理 RAM 不一定很多。

有一个prs()函数可以打印字符串。它不采用 FD 参数。它把错误信息打印到FD 2,其他字符串也可以打印到FD 2,所以它只是无条件地打印到FD 2。短代码,编译为很少的指令,因此使用最少的 RAM。

一旦事情发生了一段时间,改变他们总是面临着破坏的风险多于其改进的风险。

让我困惑的是为什么 python 的开发者甚至注意到了这一点并复制了它——在我看来,这是一个相当模糊的事实。也许这意味着我还没有找到的另一个原因。

err(s)
char *s;
{

    prs(s);
    prs("\n");
    if(promp == 0) {
        seek(0, 0, 2);
        exit();
    }
}

prs(as)
char *as;
{
    register char *s;

    s = as;
    while(*s)
        putc(*s++);
}

putc(c)
{

    write(2, &c, 1);
}

答案2

当 shell 不解释(可能内联-c)脚本且其 stdin 是终端时,它是交互式的(但请参阅下面的 POSIX shell)

在这种情况下,我们希望提示符(以及echo您为具有自己的行编辑器的 shell 键入的内容)显示在同一终端上。问题是标准输入不能保证以读+写模式打开,因此在标准输入上输出提示并不可靠。此外,在交互会话进行到一半时,人们可能想要这样做exec < other-file,这也会破坏提示显示。

明智的做法和做法zsh是确定哪个终端(使用ttyname())并重新打开它以进行读+写(在 10 以上的专用独立 fd 上)以进行用户交互。使用打开的文件描述符/dev/tty(控制终端,在大多数情况下,如果 stdin 是终端,则它会引用与 stdin 上的终端相同的终端)对我来说是有意义的,但似乎没有任何 shell 这样做它(可能是为了处理没有控制终端的情况,这种情况在某些情况下可能会发生,例如控制台上的恢复外壳)。

但是,那POSIX 规范确实规定,只有在满足上述要求的情况下,shell 才是交互式的,stderr 也是一个终端, 和POSIX 要求将提示写在 stderr 上

bash旨在符合 POSIX,因此必须遵循这些要求。

我想这是历史原因。 POSIXsh规范基于ksh88这就是其ksh88行为方式以及之前的 Bourne shell。 (尽管ttyname()已经存在于首次发布 Bourne shell 的 Unix V7 中)。

rm终端应用程序的用户交互(例如/ mv/ ...的提示find -ok)通常位于 stdin+stderr 上。 stderr 将在打开的写入模式下打开,并且在终端会话中将指向终端。 stdout 用于命令的正常输出,将其与用户交互消息分开非常重要,以便可以使用重定向来存储或后处理正常输出。将其放在 stderr(预先已知的特定 fd)上而不是打开的内部 fd 上,/dev/tty可以更轻松地删除这些消息(例如通过运行命令2> /dev/null)或以非交互方式使用命令。

但对于 shell 来说,它并不是特别有用。运行sh 2> /dev/null会使 shell 成为非交互式(这意味着不会显示提示,但会产生许多其他副作用)。这意味着可以使用 禁用提示exec 2> /dev/null,但这会产生副作用,即丢弃所有命令错误,除非您使用 运行每个命令cmd 2> something。清空PS1、PS2、PS3、PS4会好得多。

这允许用户输入来自一个终端并输出到另一个终端,但我不明白为什么有人会想要这样做。

一个可能的原因是它会更加万无一失:

  • /dev/tty如上所示,在没有控制终端的极端情况下可能不起作用,
  • ttyname()/dev当未正确填充或使用 s 时,可能无法工作chroot

请注意,在其他一些 shell 中情况更糟。csh, tcsh, rc, es, akanga,fish显示提示和您在 stdout 上键入的内容的回显(尽管fish如果 stdout 不是终端,并且csh没有行编辑器不会输出任何内容,则不会显示提示echo(请注意由终端设备的 tty 线路规则决定))。

答案3

我是这样阅读之前的答案的:因为 bash 模仿了其前身的行为,主要是 Bourne 和 Korn shell。 “一直都是这样”,大家都接受了现状,没有人质疑现状。抱歉,但这很糟糕。

IMO,真正的原因stdout受重定向影响由于命令行工具(包括 shell 脚本)通常在 *ix shell 中工作,使用管道将一个进程的输出作为输入传递到另一个进程。在这种情况下,提示将不会被看到,而只是成为管道数据的一部分,而不是您想要的。

相关内容