>&- 比 >/dev/null 更有效率吗?

jam*_*gni 58 shell file-descriptors

昨天我读了这个 SO 评论,它说在 shell 中(至少bash>&-“与”具有相同的结果>/dev/null

该评论实际上将 ABS 指南称为其信息来源。但该消息来源说>&-语法“关闭文件描述符”。

我不清楚关闭文件描述符和将其重定向到空设备这两个操作是否完全等效。所以我的问题是:是吗?

从表面上看,关闭描述符就像关闭一扇门,但将其重定向到空设备似乎打开了一扇通往地狱的大门!两者对我来说似乎并不完全相同,因为如果我看到一扇关着的门,我不会尝试从里面扔出任何东西,但是如果我看到一扇敞开的门,我会认为我可以。

换句话说,我一直想知道是否>/dev/null意味着cat mybigfile >/dev/null实际上会处理文件的每个字节并将其写入/dev/null忘记它。另一方面,如果 shell 遇到一个关闭的文件描述符,我倾向于认为(但我不确定)它不会写任何东西,尽管问题仍然是是否cat仍会读取每个字节。

这个评论>&->/dev/null应该”是一样的,但对我来说并不是那么响亮的答案。我想有一个更权威的答案,参考标准或源核心与否......

Sté*_*las 71

不,你当然希望关闭文件描述符0,1和2。

如果这样做,应用程序第一次打开文件时,它会变成 std​​in/stdout/stderr...

例如,如果你这样做:

echo text | tee file >&-
Run Code Online (Sandbox Code Playgroud)

tee(至少一些实现,比如busybox')打开文件进行写入时,它将在文件描述符1(stdout)上打开。所以tee将写入text两次file

$ echo text | strace tee file >&-
[...]
open("file", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 1
read(0, "text\n", 8193)                 = 5
write(1, "text\n", 5)                   = 5
write(1, "text\n", 5)                   = 5
read(0, "", 8193)                       = 0
exit_group(0)                           = ?
Run Code Online (Sandbox Code Playgroud)

众所周知,这会导致安全漏洞。例如:

chsh 2>&-
Run Code Online (Sandbox Code Playgroud)

并且chsh(setuid 应用程序)最终可能会在/etc/passwd.

一些工具甚至一些库试图防止这种情况发生。例如,如果 GNUtee为写入而打开的文件被分配为 0、1、2,而 busyboxtee不会,则GNU会将文件描述符移动到 2 以上的 1 。

大多数工具,如果它们无法写入 stdout(例如因为它未打开),则会在 stderr 上报告错误消息(使用用户的语言,这意味着打开和解析本地化文件需要额外的处理......),所以这将显着降低效率,并可能导致程序失败。

无论如何,它不会更有效率。该程序仍将执行write()系统调用。如果程序在第一次失败的write()系统调用后放弃写入 stdout/stderr,它只会更有效率,但程序通常不会这样做。他们通常要么出错退出,要么继续尝试。

  • 我认为如果最后一段在顶部,这个答案会更好(因为它是最直接回答 OP 问题的内容),然后它继续讨论为什么即使它基本上有效,它也是一个坏主意。但我会接受,有一个upvote。;) (4认同)
  • 我很欣赏 Stephane 以这个非常重要的安全警告开始,因为如果最后一段在顶部,它会不太明显。来自我的 +1。 (2认同)

use*_*ser 14

IOW 我一直想知道是否>/dev/null意味着cat mybigfile >/dev/null实际上会处理文件的每个字节并将其写入/dev/null忘记它。

这不是您问题的完整答案,但是是的,上面是它的工作原理。

cat读取命名文件,如果没有命名文件,则读取标准输入,并将这些文件的内容输出到其标准输出,直到遇到最后一个命名文件(包括标准输入)的 EOF。这就是它的工作。

通过添加,>/dev/null您将标准输出重定向到 /dev/null。这是一个特殊的文件(一个设备节点),它会丢弃写入其中的任何内容(并在读取时立即返回 EOF)。请注意,I/O 重定向是由 shell 提供的功能,而不是由每个单独的应用程序提供,并且名称/dev/null并没有什么神奇之处,只是在大多数类 Unix 系统上恰好存在。

同样重要的是要注意,设备节点的具体机制因操作系统而异,但 cat(在 GNU 系统中,表示 coreutils)是跨平台的(相同的源代码至少需要在 Linux 和Hurd),因此不能依赖于特定的操作系统内核。此外,如果您使用另一个名称创建 /dev/null 别名(在 Linux 上,这意味着具有相同主要/次要设备编号的设备节点),它仍然有效。并且总是有写其他地方的情况,它的行为实际上是相同的(比如,/dev/zero)。

因此,cat它不知道 /dev/null 的特殊属性,并且确实很可能一开始也不知道重定向,但它仍然需要执行完全相同的工作:它读取命名文件,并输出/那些文件到其标准输出。的标准输出cat碰巧进入一个空白并不是它cat本身所关心的。

  • @G-Man:我不知道您能否保证在所有情况下都是如此。我现在找不到证据,但我记得一些 `cat` 或 `cp` 的实现可以通过将大块源文件 `mmap` 到内存中,然后在映射区域上调用 `write()` 来工作. 如果您正在写入`/dev/null`,`write()` 调用将立即返回,而不会在源文件的页面中出错,因此它实际上永远不会从磁盘中读取。 (4认同)
  • 扩展您的答案:是的,`cat mybigfile > /dev/null` 将导致 `cat` 将 `bigfile` 的每个字节读入内存。并且,对于每 _`n`_ 个字节,它将调用 `write(1, buffer, n)`。`cat` 程序不知道,`write` 绝对不会做任何事情(除了一些琐碎的簿记)。写入 `/dev/null` 不需要处理每个字节。 (2认同)
  • 我记得当我阅读 /dev/null 设备的 Linux 内核源代码时,我被震惊了。我期待有一些精心设计的 free()ing 缓冲区等系统,但是,不,它基本上只是一个 return()。 (2认同)
  • 此外,像 GNU `cat` 这样的东西确实可以在许多平台上运行,但是随便看一眼源代码就会发现很多 `#ifdef`:它实际上并不是在所有平台上运行的相同代码,而且有很多系统相关部分。 (2认同)