当目标文件尚不存在时,使用 `>>` 重定向是否等同于 `>`?

Kam*_*ski 81 bash redirection sh

考虑像 Bash 或 sh 这样的 shell。在目标文件存在的情况下,>和之间的基本区别>>表现出来:

  • > 将文件截断为零大小,然后写入;
  • >> 不截断,它写入(附加)到文件的末尾。

如果文件不存在,则以零大小创建;然后写到。这对两个运营商都是如此。当目标文件尚不存在时,运算符似乎是等效的。

他们真的吗?

Kam*_*ski 109

tl;博士

>>本质上是“始终寻求文件结尾”,同时>维护指向最后写入位置的指针。


完整答案

(注意:我所有的测试都是在 Debian GNU/Linux 9 上完成的)。

另一个区别

不,它们不是等价的。还有一个区别。无论目标文件之前是否存在,它都可能表现出来。

要观察它,请运行一个生成数据并使用>>>(例如pv -L 10k /dev/urandom > blob)重定向到文件的进程。让它运行并更改文件的大小(例如使用truncate)。你会看到它>保持它的(增长的)偏移量,同时>>总是附加到最后。

  • 如果您将文件截断为较小的大小(它可以是零大小)
    • >不会在意,它会在其所需的偏移量处写入,就好像什么也没发生一样;在截断偏移量超出文件末尾之后,这将导致文件恢复其旧大小并进一步增长,丢失的数据将用零填充(如果可能,以稀疏的方式);
    • >> 将附加到新的末尾,文件将从其截断的大小开始增长。
  • 如果放大文件
    • >不会在意,它会在其所需的偏移量处写入,就好像什么也没发生一样;刚改变大小后,offset就在文件内部的某个地方,这会导致文件停止增长一段时间,直到offset到达新的末尾,然后文件才会正常增长;
    • >> 将附加到新的末尾,文件将从其放大的大小开始增长。

另一个例子是>>在数据生成过程运行并写入文件时附加(使用单独的)一些额外的东西。这类似于放大文件。

  • 生成过程>将以其所需的偏移量写入并最终覆盖额外的数据。
  • 生成过程>>将跳过新数据并将其追加(可能会发生竞争条件,两个流可能会交错,仍然不应该覆盖任何数据)。

例子

在实践中重要吗?有这个问题

我正在运行一个在标准输出上产生大量输出的进程。将其全部发送到文件 [...] 我可以使用某种日志轮换程序吗?

这个答案说解决方案是logrotate使用这样的copytruncate选项:

创建副本后原地截断原始日志文件,而不是移动旧日志文件并可选择创建新日志文件。

根据我上面写的内容,重定向 with>会立即使截断的日志变大。稀疏将节省一天,不应浪费大量磁盘空间。然而,每个连续的日志都会有越来越多的前导零,这些零是完全不必要的。

但是如果logrotate在不保留稀疏性的情况下创建副本,则每次创建副本时,这些前导零将需要越来越多的磁盘空间。我还没有研究过工具行为,它可能足够智能,可以即时进行稀疏或压缩(如果启用了压缩)。尽管如此,零可能只会造成麻烦或充其量是中性的;他们没有什么好处。

在这种情况下,使用>>而不是>明显更好,即使目标文件即将被创建。


表现

正如我们所看到的,这两个操作符不仅在开始时而且在以后的行为都不同。这可能会导致一些(微妙的?)性能差异。目前我没有有意义的测试结果来支持或反驳它,但我认为你不应该自动假设它们的性能总体上是相同的。

  • 在系统调用级别,`>>` 使用 [`O_APPEND` 标志到 `open()`](http://man7.org/linux/man-pages/man2/open.2.html)。实际上,`>` 使用了`O_TRUNC`,而`>>` 没有。`O_TRUNC | 的组合 O_APPEND` 也是可能的,shell 语言只是不提供该功能。 (11认同)
  • 所以 `>>` 本质上是“总是寻找文件结尾”,而 `>` 维护一个指向最后写入位置的指针。似乎它们的工作方式也可能存在一些微妙的性能差异...... (10认同)
  • @jjmontes,标准来源是 POSIX:http://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html#tag_18_07 但当然 Bash 的手册也有关于重定向运算符的描述,包括非标准它支持的:https://www.gnu.org/software/bash/manual/html_node/Redirections.html (3认同)
  • @ilkkachu 我发现这很有趣,因为它解释了有关 O_APPEND 的详细信息,我在您发表评论后对此感到疑惑:):/sf/ask/80811251/ (2认同)
  • @Mokubai,任何理智的操作系统在打开时都会拥有文件长度,并且检查标志并将偏移量移动到末尾应该会在所有其他簿记中消失。不过,尝试在每个“write()”之前使用“lseek()”模拟“O_APPEND”会有所不同,这会产生额外的系统调用开销。(当然这是行不通的,因为另一个进程可以在其间进行“write()”。) (2认同)