一年前,我为有关如何在 Linode 上安装 NixOS 的教程。
似乎大多数涉及将 iso 文件放在安装介质上的教程总是使用dd
,这就是为什么“现在 dd 还有意义吗?”没有回答我的问题。本教程最初也使用过dd
,我对其进行了稍微修改,以获取 sha256 校验和来验证 iso,同时使用 管道从源中传输它tee
,如下所示:
curl -L $iso | tee >(dd of=/dev/sda) | sha256sum
但在我看来,dd
这里有点多余,而且实际上比下面的慢很多:
curl -L $iso | tee /dev/sda | sha256sum
几个月前,在帮助别人遵循教程时,我记得他们在使用更简单的方法时遇到了问题,但是当我上周末刚刚尝试过两次单独的安装时,它似乎工作得很好,而且速度也相当快。
此修改是否足够可靠,可以提交更新教程的拉取请求?
或者我只是幸运地让它工作,并且dd
实际上更安全、更可靠地用于创建安装介质 - 因此我们应该按原样保留教程?
答案1
使用dd
并不更安全、更快或更不可靠。事实上,这里引入了两种额外的失败风险。如果人们手动遵循这些说明,这两种风险在实践中都不太可能成为问题,但如果这些说明位于自动化脚本中,则这两种风险都将是严重的错误。
Bug:竞争条件
观察:
bash-5.0$ echo hello | tee >(sleep 1; echo done); echo next step
hello
next step
bash-5.0$ done
在 bash 中,输出过程替换是异步的。当命令包含进程替换时>(…)
,它不会等待进程替换完成。
因此,当… | tee >(dd of=/dev/sda) | sha256sum
返回时,可能有数据仍在通过 传输dd
。这不太可能持续足够长的时间以供人类做出反应并键入另一个命令,但它可能会破坏运行其他命令(例如eject
或mount
之后)的脚本。
Bug:缺少错误检测
让我们开始一个一切正常的名义案例。
bash-5.0$ head -c 1m </dev/zero | tee >(cat >/dev/null) | wc -c; echo $?
1048576
0
现在让我们看看如果数据写入命令失败会发生什么情况。
bash-5.0$ head -c 1m </dev/zero | tee >(false) | wc -c; echo $?
8192
0
该命令具有成功状态,因为管道的退出状态仅取决于右侧。这个想法是,如果将数据生成器通过管道传输到数据处理器,则数据处理器的工作就是检测故障。不幸的是,这仅适用于数据格式允许数据处理器检测故障的情况,但通常情况并非如此,特别是此处并非如此。
请注意,tee
一旦无法写入连接到的管道,就彻底放弃false
。由于false
从未读取任何数据,因此通过 to 的唯一数据wc -c
是两个PIPE_BUF
(一个tee
写入两个管道,另一个tee
仅写入管道 towc
但未能写入管道 to false
)。根据false
退出与tee
写入管道和wc
消耗数据的相对时间,可能只有 1 个或 0 个PIPE_BUF
通过。
tee
通过设置该选项可以检测到故障pipefail
。 (这种可能性存在于 ksh、bash 和 zsh 中,但不存在于普通 sh 中。)
bash-5.0$ set -o pipefail; head -c 1m </dev/zero | tee >(false) | wc -c; echo $?
8192
141
tee
无法写入管道,因此它死于 SIGPIPE,对应的 shell 状态为 128 + SIGPIPE 的数值(在 Linux 上为 13)。由于该pipefail
选项,这会导致整个管道以相同的状态退出。
请注意,管道反映的是 的失败tee
,而不是直接反映进程替换中命令的失败。如果进程替换中的命令成功读取了所有数据但未成功处理,则不会检测到错误。
bash-5.0$ head -c 1m </dev/zero | tee >(cat >/dev/null; false) | wc -c; echo $?
1048576
0
wc -c
处理了所有数据。cat >/dev/null; false
模拟未正确处理所有输入的命令。尽管如此,该命令的状态表明已成功。
在您的实际示例中,这意味着如果数据末尾存在错误,例如因为目标设备比图像小得多,则不会检测到此错误(除非通过来自 的错误消息dd
) 。
简单、正确的解决方案
set -o pipefail
curl -L $iso | tee /dev/sda | sha256sum
或者,可以说更简单:
curl -L $iso | tee >/dev/sda >(sha256sum)
请注意,如果没有pipefail
,第二个命令即使curl
失败也会成功。然而,这种失败肯定会导致错误的校验和。
关于 dd 使用的一般说明
似乎大多数涉及将 iso 文件放在安装介质上的教程总是使用 dd,这就是为什么“现在 dd 仍然相关吗?”没有回答我的问题
嗯,或多或少确实如此。具体来说,它回答了是否有任何目的的问题dd
:没有。它没有涵盖使用dd
这种特定方式时遇到的具体问题,这次实际上并不是由于dd
其本身。
大多数教程使用的原因dd
是大多数教程使用dd
.这是一个自我延续的传奇。人们使用它是dd
因为他们在其他地方看到过它的使用,尽管他们并不真正理解为什么。它的语法与其他命令不同,因此显得有些神秘和强大。但在dd of=/dev/sda
,所有的力量都在/dev/sda
,却没有dd
。这只是一种自命不凡、脆弱的写作方式cat >/dev/sda
。