![为什么 bash 交互式 shell 默认情况下会写入其提示符并将其交互式输入回显到 stderr?](https://linux22.com/image/9836/%E4%B8%BA%E4%BB%80%E4%B9%88%20bash%20%E4%BA%A4%E4%BA%92%E5%BC%8F%20shell%20%E9%BB%98%E8%AE%A4%E6%83%85%E5%86%B5%E4%B8%8B%E4%BC%9A%E5%86%99%E5%85%A5%E5%85%B6%E6%8F%90%E7%A4%BA%E7%AC%A6%E5%B9%B6%E5%B0%86%E5%85%B6%E4%BA%A4%E4%BA%92%E5%BC%8F%E8%BE%93%E5%85%A5%E5%9B%9E%E6%98%BE%E5%88%B0%20stderr%EF%BC%9F.png)
我刚刚注意到 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 中工作,使用管道将一个进程的输出作为输入传递到另一个进程。在这种情况下,提示将不会被看到,而只是成为管道数据的一部分,而不是您想要的。