假设我想将某个命令的输出通过管道传输到一个文件中。那么,简单地将其通过管道传输到一个文件中,与直接通过管道传输到文件中,有什么区别吗?
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-command
dd 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。
同样,所有边缘情况都需要单独的问题,但正如您所看到的,如果我们严格地从一个与另一个的角度来谈论,即使存在差异,除了极端边缘情况外,我们怀疑它是否会被注意到。
希望这有帮助,我不得不说这个问题很有趣,我花了一段时间才思考它。