文件重定向与 dd

文件重定向与 dd

假设我想将某个命令的输出通过管道传输到一个文件中。那么,简单地将其通过管道传输到一个文件中,与直接通过管道传输到文件中,有什么区别吗?

some-command > file

dd并通过管道传输

some-command | dd $MAYBE_SOME_OPTIONS_LIKE_BS of=file

这个问题主要是为了满足我的好奇心,除非后一种形式有一些不可忽略的好处或用例,否则在实践中我会省去手指的练习而选择前者。

答案1

直接访问file

some-command > file
some-command | dd $MAYBE_SOME_OPTIONS_LIKE_BS of=file

在第一种情况下,some-command它知道它写入了file,因此它可以检索元数据(如 mtime 或大小),无论出于何种原因。此类元数据对于常规文件具有有用的意义,但对于未命名管道来说,它们毫无意义。

some-command甚至可能有兴趣打开它的标准输出指向的内容进行读取,这对于常规文件来说相当安全,但在第二种情况下显然不是正确的做法。

但我认为这些情况非常罕见。


可寻址与不可寻址

的行为some-command可能取决于其 stdout 是否可寻址。通常情况并非如此,尽管这是可能的。常规文件是可寻址的(您可以在任何位置读取/写入)。管道不可寻址。

dd本身就是一个有效的例子。考虑some-command一下dd if=/dev/zero bs=1 count=1 seek=1。你的两个例子变成:

dd if=/dev/zero bs=1 count=1 seek=1 > file
dd if=/dev/zero bs=1 count=1 seek=1 | dd of=file

第一个命令会起作用,它会留下file两个空字节。在第二个命令中,第一个命令dd会抱怨它无法查找,它会退出;第二个命令dd不会得到任何结果,也不会写入任何内容,并且不会出现任何错误。

我承认seek这并不完全是“将某个命令的输出通过管道传输到文件中”。但一般来说,some-command可以检测其 stdout 是否可搜索,然后流式传输不同的输出没有实际上是在寻找。这个 shell 函数有点像是一个概念证明:

is-seekable() {
  if </dev/null dd bs=1 count=0 seek=1 conv=notrunc 2>/dev/null; then
    echo seekable
  else
    echo non-seekable
  fi
  }

尝试这些操作并在每次操作后检查文件:

is-seekable > file
is-seekable | dd of=file

了解了以上所有内容后,在某种情况下我会考虑您的第二条命令:如果some-command在两种情况下(例如我们的is-seekable函数)确实表现不同,并且我希望它“认为”它正在写入管道,但我希望输出到文件中。那么是的。虽然我宁愿使用cat而不是dd(这可能无关紧要)。例如,我可能想要non-seekable从中获取is-seekable并将其写入文件:

is-seekable | cat > file

但前提是它确实会产生影响。否则,它就会成为臭名昭著的“无用使用cat”。或者在你的情况下是“无用使用dd”;或者如果你对退出状态处理不当,甚至是“有害使用”。


退出状态

现在尝试以下两个例子:

false > file
false | dd of=file

如果您$?在第二个之后检查,您会发现它是0(除非dd失败)。您的第二个案例丢弃了退出状态some-command(通常;例如在 Bash 调查中set -o pipefail,这是不可移植的)。

因此,在第二种情况下,some-command无论出于何种原因,都可能会失败,而你(或者更确切地说,你的脚本的逻辑)不会知道这一点。这足以避免不必要的管道dd(或根本不必要的管道)。


权限

另一个细微差别:使(子)shell 在生成(执行)some-command > file之前打开文件;以自己的名义打开文件。some-commanddd of=file

有多种方法可以授予dd比任何其他“常规”命令更多的访问权限。换句话说,有多种方法可以让 plain 的dd行为更像sudo dd没有密码。你不应该这样做,希望没有人这样做,但这是可能的。

您可以根据需要授予访问权限:sudo dd。当尝试写入受限制的文件时,它很有用。分析一下:

some-command > restricted_file               # doesn't work
sudo some-command > restricted_file          # doesn't work
some-command | sudo dd > restricted_file     # doesn't work
sudo sh -c 'some_command > restricted_file'  # works, but it runs some_command as root
                                             #  you may not want this
some-command | sudo dd of=restricted_file    # works
some-command | sudo tee restricted_file      # works, more common, possibly with
                                             #  > /dev/null to suppress output to tty

我认为这非常接近“不可忽略的利益或用例”。


也许有一些选择

显然$MAYBE_SOME_OPTIONS_LIKE_BS可以改变dd行为。我理解诸如此类的选项conv=swab超出了范围,因为您希望file在两种情况下接收相同的数据。

但仍有一些选择可以带来改变。例如:

  • status=progress在处理每个输入块时,将在 stderr 上打印传输速率和容量统计信息;

  • oflag=noatime不会更新文件的访问时间戳;

  • oflag=nolinks如果文件有多个硬链接将会失败;

  • 更多的

上述示例是 GNU 扩展,不可移植。可移植的dd特点是这里


最后还有KISS原则:保持简单。您的第一个案例非常简单。

答案2

用户不会注意到任何差异。从性能角度来看,也怀疑会存在差异。如果你考虑这两种情况,就会发现它们各有利弊,但这些在很大程度上取决于你想要实现的目标。

例如:cat file | dd of=file2 vs cat file > file2。这里的区别是,cat 将打开一个文件,dd 将继承该流,除非您指定块大小或某种写入标志,否则它将立即开始写入 fs 堆栈。

如果您不使用 dd 的任何功能,那么您实际上只是分叉了一个中间人,添加了一个进程,我认为这种双重分叉与仅让 dd 处理 if=file of=newfile 相比,性能损失最小。在这种情况下,优点是您可以在 dd 中查找/跳过,而且只有一个进程。

如果您指的是文件重定向与 dd,我会说如果您不为 dd 提供任何标志/参数,则没有区别。在这种情况下,您可以将其用作中间人缓冲区,但还有更好的替代方案,例如:cat file | buffer | gzip > gzipped content。例如,上面堆叠的调用,假设您有一个超快的源和一个慢速目标,通过两者之间的缓冲区,您可以确保接收端始终有一个繁忙的输入队列,如果您正在压缩,您可以确保您的 CPU 不会等待 IO。

同样,所有边缘情况都需要单独的问题,但正如您所看到的,如果我们严格地从一个与另一个的角度来谈论,即使存在差异,除了极端边缘情况外,我们怀疑它是否会被注意到。

希望这有帮助,我不得不说这个问题很有趣,我花了一段时间才思考它。

相关内容