dd
dd 是一个核心实用程序,其主要目的是复制文件,并可以选择在复制过程中进行转换。
与 cp 类似,默认情况下 dd 对文件进行逐位复制,但具有更底层的 I/O 流控制功能。
status=progress
选项。安装
dd 是 GNU coreutils 的一部分。有关软件包中的其他实用程序,请参阅 Core utilities。
磁盘克隆与恢复
dd 命令是一个简单但功能多样且强大的工具。它可以用于从源到目标逐块复制,而无需考虑其文件系统类型或操作系统。一种方便的方法是从 Live 环境(例如 Live CD)中使用 dd。
if=
) 和输出文件 (of=
) 的顺序,不要颠倒它们!始终确保目标驱动器或分区 (of=
) 的大小等于或大于源 (if=
)。克隆分区
从物理磁盘 /dev/sda
的分区 1 克隆到物理磁盘 /dev/sdb
的分区 1
# dd if=/dev/sda1 of=/dev/sdb1 bs=64K conv=noerror,sync status=progress
of=
(示例中的 sdb1
)不存在,dd 将创建一个以此名称命名的文件,并开始填满您的根文件系统。克隆整个硬盘
从物理磁盘 /dev/sda
克隆到物理磁盘 /dev/sdb
# dd if=/dev/sda of=/dev/sdb bs=64K conv=noerror,sync status=progress
这将克隆整个驱动器,包括分区表、引导加载器、所有分区、UUID 和数据。
bs=
设置块大小。默认为 512 字节,这是自 1980 年代初期以来硬盘驱动器的“经典”块大小,但不是最方便的。使用更大的值,例如 64K 或 128K。另外,请阅读下面的注释,因为这不仅仅是“块大小”的问题——它还会影响读取错误的传播方式。有关详细信息并找出适合您用例的最佳 bs 值,请参阅 [1] 和 [2]。noerror
指示 dd 继续操作,忽略所有读取错误。dd 的默认行为是在任何错误处停止。sync
如果在块中的某个位置存在任何读取错误,则在块的末尾用零填充输入块,以便数据偏移保持同步(如果您怀疑可能存在读取错误,请参阅下文有关同步时读取错误行为的详细说明)。status=progress
显示周期性的传输统计信息,可用于估计操作何时可能完成。
dd 实用程序在技术上具有“输入块大小” (IBS) 和“输出块大小” (OBS)。当您设置 bs
时,您实际上同时设置了 IBS 和 OBS。通常,如果您的块大小为 1 MiB,dd 将读取 1024×1024 字节并写入相同数量的字节。但是,如果发生读取错误,事情就会出错。许多人似乎认为,如果您使用 noerror,sync
选项,dd 将“用零填充读取错误”,但事实并非如此。根据文档,dd 将在完成读取后将 OBS 填充到 IBS 大小,这意味着在块的末尾添加零。这意味着,对于磁盘,由于块开头存在单个 512 字节的读取错误,整个 1 MiB 将变得混乱:12ERROR89 将变为 128900000 而不是 120000089。
如果您确信您的磁盘不包含任何错误,您可以继续使用更大的块大小,这将使您的复制速度提高数倍。例如,在简单的 Celeron 2.7 GHz 系统上,将 bs 从 512 更改为 64K,复制速度从 35 MB/s 提高到 120 MB/s。但请记住,源磁盘上的读取错误最终将成为目标磁盘上的块错误,即单个 512 字节的读取错误将搞乱整个 64 KiB 输出块。
- 要重新获得 ext2/3/4 文件系统的唯一 UUID,请在每个分区上使用
tune2fs /dev/sdXY -U random
。对于交换分区,请改用mkswap -U random /dev/sdXY
。 - 如果您正在克隆 GPT 磁盘,则可以使用 sgdisk 来随机化磁盘和分区 GUID,并重新获得其唯一性。
- 来自 dd 的分区表更改不会被内核注册。要在不重启的情况下通知更改,请使用类似 partprobe(GNU Parted 的一部分)的实用程序。
备份分区表
请参阅 fdisk#备份和恢复分区表 或 gdisk#备份和恢复分区表。
创建磁盘镜像
从 Live 介质启动,并确保未从源硬盘驱动器挂载任何分区。
然后挂载外部硬盘驱动器并备份驱动器
# dd if=/dev/sda conv=sync,noerror bs=64M status=progress | lz4 -z > /path/to/backup.img.lz4
如有必要(例如,当结果文件将存储在 FAT32 文件系统上时),将磁盘镜像拆分为多个部分(另请参阅 split(1))
# dd if=/dev/sda conv=sync,noerror bs=64M status=progress | lz4 -z | split -a3 -b2G - /path/to/backup.img.lz4
如果本地磁盘空间不足,您可以将镜像通过 ssh 发送
# dd if=/dev/sda conv=sync,noerror bs=64M status=progress | lz4 -z | ssh user@local dd of=backup.img.lz4
最后,保存有关驱动器几何形状的额外信息,以便解释镜像中存储的分区表。其中最重要的是扇区大小。
# fdisk -l /dev/sda > /path/to/list_fdisk.info
恢复系统
要恢复您的系统
# lz4 -dc /path/to/backup.img.lz4 | dd of=/dev/sda status=progress
当镜像已被拆分时,请改用以下命令
# cat /path/to/backup.img.lz4* | lz4 -dc | dd of=/dev/sda status=progress
备份与恢复 MBR
在更改磁盘之前,您可能需要备份驱动器的分区表和分区方案。您还可以使用备份将相同的分区布局复制到多个驱动器。
MBR 存储在磁盘的前 512 个字节中。它由 4 部分组成
- 前 440 个字节包含引导代码(引导加载器)。
- 接下来的 6 个字节包含磁盘签名。
- 接下来的 64 个字节包含分区表(4 个条目,每个条目 16 字节,每个主分区一个条目)。
- 最后 2 个字节包含引导签名。
要将 MBR 保存为 mbr_file.img
# dd if=/dev/sdX of=/path/to/mbr_file.img bs=512 count=1
您也可以从完整的 dd 磁盘镜像中提取 MBR
# dd if=/path/to/disk.img of=/path/to/mbr_file.img bs=512 count=1
要恢复(小心,这会破坏现有的分区表,并因此破坏对磁盘上所有数据的访问)
# dd if=/path/to/mbr_file.img of=/dev/sdX bs=512 count=1
如果您只想恢复引导加载程序,而不是主分区表条目,只需恢复 MBR 的前 440 个字节
# dd if=/path/to/mbr_file.img of=/dev/sdX bs=440 count=1
要仅恢复分区表,必须使用
# dd if=/path/to/mbr_file.img of=/dev/sdX bs=1 skip=446 count=64
移除引导加载器
要擦除 MBR 引导代码(如果您必须完全重新安装另一个操作系统,这可能很有用),只需要将前 440 个字节清零即可
# dd if=/dev/zero of=/dev/sdX bs=440 count=1
正如一些读者可能已经意识到的,与其它实用程序相比,dd(1) 核心实用程序具有不同的命令行语法。此外,虽然支持 在其他通用实用程序中找不到的一些独特功能,但如果应用于特定场景,它的一些默认行为要么不太理想,要么 可能容易出错。因此,用户可能希望使用一些在某些方面更好的替代方案来代替 dd 核心实用程序。
尽管如此,仍然值得注意的是,由于 dd 是一个 核心实用程序,默认安装在 Arch 和许多其他系统上,因此如果在新系统上安装新软件包不方便,那么它比某些替代方案或更专业的实用程序更可取。
为了涵盖上述两个方面,本节专门总结 dd(1) 核心实用程序的特性,这些特性在其他通用实用程序中很少见——以类似于 pacman/Rosetta 文章的形式,但减少了示例数量,以检查 dd 的特性(如子章节“提示:”框中 i.e. 或 To 子句所示),无论是实践还是伪代码。
有关更多替代方案,请参阅 coreutils#dd 替代方案。
原地逐块修补二进制文件
在自动化 shell 脚本中使用 dd 作为功能有限的二进制文件修补程序是一种常见的做法,因为它支持
- 在写入之前,通过给定的偏移量
seek
输出文件。 - 写入输出文件(通过添加
conv=notrunc
选项,而不截断输出文件的大小)。
这是一个修改 cpio(5) § Portable ASCII Format 存档中第一个成员的时间戳部分的示例,该部分从文件的第 49 个字节开始(如果您喜欢十六进制表示法,则偏移量为 0x30
)
$ touch a-randomly-chosen-file $ bsdtar -cf example-modify-ts.cpio --format odc -- a-randomly-chosen-file
$ printf '%011o' "$(date -d "2019-12-21 00:00:00" +%s)" | dd conv=notrunc of=example-modify-ts.cpio seek=48 oflag=seek_bytes
seek_bytes
输出标志,用于在开始 write(2) 到输出之前,以字节偏移量而不是块偏移量来查找输出。- dd 的输入文件是与利用 splice(2) 系统调用的程序连接的管道,并且用户希望避免 dd 不必要的 userspace I/O 以获得更好的性能
- 或者,为了避免在 shell 脚本循环中频繁 fork(2) 以减少性能损失
$ zsh
$ local +xr openToWriteFD $ zmodload zsh/system $ sysopen -wu openToWriteFD example-modify-ts.cpio $ sysseek -u $openToWriteFD 48 $ printf '%011o' "$(date -d "2019-12-21 00:00:00" +%s)" >&${openToWriteFD} ... $ : finally close the fd {openToWriteFD}>&-
打印 VFAT 文件系统镜像的卷标
要读取文件系统 VFAT 的卷标 镜像文件,其总长度应为 11 个字节,并用 ASCII 空格填充,偏移量为 0x047
$ truncate -s 36M empty-hole.img $ mkfs.fat -F 32 -n LabelMe empty-hole.img
$ dd iflag=skip_bytes,count_bytes count=11 skip=$((0x047)) if=empty-hole.img | sed -e 's% *$%%'
$ socat -u -,seek=$((0x047)),readbytes=11 - < empty-hole.img | sed -e 's% *$%%'
管道命令之间的 Sponge
在以下示例中,为了避免在输出端阻塞时间超过预期时输入端不必要的长时间 TCP 连接,可以在两个命令之间放置一个 dd,其输出块大小肯定大于输入,但仍然合理地小于可用内存
$ curl -qgsfL http://example.org/mirrors/ftp.archlinux.org/mirrored.md5deep | dd ibs=128k obs=200M | poor-mirroring-script-that-perform-mirroring-on-input-paths-line-by-line-wo-buffer-entire-list-first
传输大小受限的数据
在数据流 shell 脚本中使用 dd 来限制管道命令可能消耗的数据总长度是一种通用做法。例如,使用流式 shell 脚本函数检查 ustar 标头块(tar(5) § POSIX ustar Archives)
count
选项的参数中的 B
后缀是 GNU coreutils v9.1 的一个 新引入的 功能,它具有与 count_bytes 输入标志 相同的效果,但可能会与 count=256k
等形式的选项混淆,后者指示 dd 复制 262144 个输入块而不是字节。hexdump-field() { set -o pipefail printf '%s[%d]:\n' $1 $2 dd count=${2}B status=none | hexdump -e $2'/1 "%3.2x"' -e '" | " '$2'/1 "%_p" "\n"' } inspect-tar-header-block() { local -a hdrstack=( name 100 mode 8 uid 8 gid 8 size 12 mtime 12 checksum 8 typeflag 1 linkname 100 magic 6 version 2 uname 32 gname 32 devmajor 8 devminor 8 prefix 155 pad 12 ) set - ${hdrstack[@]} while test $# -gt 0; do hexdump-field $1 $2 || return shift 2 done }
$ bsdtar -cf - /dev/tty /dev/null 2>&- | dd count=1 skip=1 status=none | inspect-tar-header-block
- 具有内置查找功能的 shell(如前一个子章节中的替代方案中已经提到的。)
- 或者,类似 Bourne 的 shell(例如 bash),在 xxd(1) § s 的帮助下,在 shell 打开的文件描述符上进行一次性 lseek(2),
ls -l /proc/self/fd
在 bash 中分配文件描述符)$ bash
$ exec 9<dummy-but-rather-large.img $ xxd -g 0 -l 0 -s $((0x47ffff)) <&9 $ pv -qSs 104857601200 <&9 | program-that-process-load-of-data-but-does-not-limit-read-length-as-desired-nor-support-offset-read $ exec 9<&-
count=0
和 skip
选项来替换上述示例中 xxd(1) § s 的用法。将可引导磁盘镜像写入块设备,可选显示进度信息
有关包含潜在最不适合该情况的 dd 的通用实用程序的示例,请参阅 USB 闪存安装介质#使用基本命令行工具。
故障排除
部分读取:复制的数据小于请求量
如果当前没有可用的完整输入块,则使用 dd 创建的文件最终大小可能会小于请求的大小,如 文档所述
- 此外,如果没有指定数据转换
conv
操作数[即本 wiki 文章中的选项],则输入会在读取后立即复制到输出,即使它小于块大小。
在 Linux 上,从 pipe(7) 读取时,或者读取设备文件(如 /dev/urandom
和 /dev/random
)时,底层的 read(2) 系统调用可能会提前返回(即部分读取)(例如,由于 底层内核设备驱动程序的硬编码限制)。这使得当使用 bs
结合 count=n
选项时,复制的数据总大小小于预期,其中 n
限制了要复制到输出的(潜在部分)输入块的最大数量。
dd 可能会警告您此类问题,但这不能保证
dd: warning: partial read (X bytes); suggest iflag=fullblock
解决方案是按照警告的提示进行操作,除了输入文件选项之外,还要在 dd 命令中添加 iflag=fullblock
选项。例如,要创建一个总长度为 40 兆字节的随机数据填充的新文件
$ dd if=/dev/urandom of=new-file-filled-by-urandom.bin bs=40M count=1 iflag=fullblock
count=n
选项时,如果需要擦除 设备的一部分 或 文件,建议或始终强烈建议将 iflag=fullblock
选项添加到 dd 命令中。当从管道读取时,iflag=fullblock
的替代方案是将 bs
限制为 linux/limits.h 中定义的 PIPE_BUF
常量值,以使 pipe(7) I/O 原子化。例如,要准备一个总长度为 5 兆字节的随机字母数字字符串填充的文本文件
$ LC_ALL=C tr -dc '[:alnum:]' </dev/urandom | dd of=passtext-5m.txt bs=4k count=1280
由于输出文件不是管道,因此可以首选使用 ibs
和 obs
选项分别为(输入)管道和(输出)磁盘文件设置块大小。例如,为输出文件设置更高效的块大小
$ LC_ALL=C tr -dc '[:alnum:]' </dev/urandom | dd of=passtext-5m.txt ibs=4k obs=64k count=1280
PIPE_BUF
常量定义的值)可能已经是最佳的。总传输字节计数读数错误
如果在写入输出时遇到错误(即部分写入,例如由 SIGPIPE、介质故障或意外断开目标网络块设备引起),则总传输字节计数读数可能大于实际值,如下面的概念验证所示,其中第二个 dd 显然不会读取超过 512200 字节,但第一个 dd 实例仍然报告不准确的字节计数 512400 字节
$ yes 'x' | dd bs=4096 count=512400B | dd ibs=1 count=512200 status=none >/dev/null 125+1 records in 125+1 records out 512400 bytes (512 kB, 500 KiB) copied, 10.7137 s, 47.8 kB/s
当恢复像上述 PoC 这样的中断传输时,建议仅依赖于已复制的完整输出块数量的读数,如“+”号之前的数字所示。
iflag=fullblock
选项后,部分 I/O 块计数仍大于 1,这意味着部分 I/O 发生不止一次。在这种情况下,为了可靠地恢复您的传输进度,建议- 使用 ddrescue 重新运行传输,以更灵活地处理潜在故障介质上的部分读取。
- 使用 dd_rescue(1) 重新运行直接 I/O 传输,以防写入网络连接不良的 nbd。
- 避免写入故障介质。