cat | dd 行为不一致

cat | dd 行为不一致

从给定的文件,我需要创建一个用零填充到特定大小的副本。

如果您创建一个包含以下内容的文件。

echo test >testfile

以下命令的输出不一致。

cat testfile /dev/zero | dd bs=256k count=1 status=none | od -c

这是我期望的输出。

0000000   t   e   s   t  \n  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
1000000

但您也会随机获得以下任一项。

0000000   t   e   s   t  \n
0000005
0000000   t   e   s   t  \n  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0400000  \0  \0  \0  \0  \0
0400005

为什么这个命令的行为不一致?

即使 dd 在第一个文件末尾切断管道,128k 的结果也很奇怪。我在 16.04、18.04 和 19.04 系统下得到了同样不一致的结果。

答案1

您需要指定完整的块。尝试:

cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | od -c

文档

man dd

fullblock
        累积输入的完整块(仅 iflag 可用)

例子

观察发现,如果没有fullblock,字节数是不一致的:

$ cat testfile /dev/zero | dd bs=256k count=1 status=none | wc -c
5
$ cat testfile /dev/zero | dd bs=256k count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k count=1 status=none | wc -c
5

通过iflag=fullbock,我看到了一致的完整字节数:

$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144
$ cat testfile /dev/zero | dd bs=256k iflag=fullblock count=1 status=none | wc -c
262144

答案2

问题的核心有两个方面,一部分是短板,一部分是片面read()POSIX 规范

部分输入块是 read() 返回的小于输入块大小的块。

这是管道的典型情况,问题中也正是如此。一种解决方案是使用 GNU 扩展iflag=fullblock,这是 Ubuntu 使用的版本。来自GNU dd 手册

请注意,如果输入可能返回短读取(例如从管道读取时的情况),“iflag = fullblock”将确保“count =”对应于完整的输入块,而不是传统 POSIX 指定的计数输入读取操作的行为。

POSIX dd微软ddFreeBSDdd- 这些没有这样的选项(尽管有请求将其添加到 POSIX 规范中)。那么我们如何编写可移植脚本,以便dd从 Ubuntu 移植到 FreeBSD 呢?好吧,问题的一部分是标志count=1。它告诉要执行dd多少次read()调用。尝试执行多个跟踪dd if=/dev/urandom | strace -e read dd of=/dev/null bs=256k count=1,您将看到始终只有一个read(),并且通常是部分的。(另请注意,如果您看到读取了 262144 个字节而不是 256,000 个字节,请不要感到惊讶,因为 256k 是 256*1024=262144)

解决方案是翻转参数,即使块大小bs=1count=256k。这样我们就可以确保没有部分读取,并且我们总是读取 1 个字节,但我们会这样做 256k 次。是的,这会慢很多,并且对于 GB/TB 范围内的数据,需要更长的时间。在我的测试中,速度iflag=fullblock大约快 100 倍(256k 字节上 5 毫秒和 700 毫秒之间的差异)。但是,优点是它是可移植的,并且不必依赖 GNUdd扩展,尤其是您不能总是安装 GNUdd

相关内容