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=64M status=progress
of=(示例中的 sdb1)不存在,dd 将创建一个以此命名的文件,并开始填充您的根文件系统。克隆整个硬盘
从物理磁盘 /dev/sda 到物理磁盘 /dev/sdb
# dd if=/dev/sda of=/dev/sdb bs=64M status=progress
这将克隆整个驱动器,包括分区表、引导加载程序、所有分区、UUID 和数据。
bs=设置块大小(以字节为单位),这是 dd 一次操作的数据量。较大的值通常会产生更好的性能,但会使用更多内存。默认值为 512 字节,这是自 20 世纪 80 年代初以来硬盘的“经典”块大小,但速度很慢。使用较大的值,至少 32M 或 64M。有关详细信息以及如何为您的用例确定最佳 bs 值,请参阅 [1] 和 [2]。status=progress显示周期性的传输统计信息,可用于估算操作何时可能完成。
conv 的 noerror 和/或 sync 选项来覆盖此行为。相反,请使用 ddrescue 进行数据恢复。- 要重新获得 ext2/3/4 文件系统的唯一 UUID,请在每个分区上使用
tune2fs /dev/sdXY -U random。对于交换分区,请使用mkswap -U random /dev/sdXY。 - 如果您正在克隆 GPT 磁盘,您可以使用 sgdisk 来随机化磁盘和分区 GUID,并重新获得它们的唯一性。
- dd 对分区表的更改不会被内核注册。要不重启即通知更改,请使用 partprobe(GNU Parted 的一部分)之类的实用程序。
备份分区表
请参阅 fdisk#Backup and restore partition table 或 gdisk#Backup and restore partition table。
创建磁盘镜像
从 Live 介质启动,并确保未挂载源硬盘的任何分区。
然后挂载外部硬盘并备份驱动器
# dd if=/dev/sda bs=64M status=progress | lz4 -z > /path/to/backup.img.lz4
如果需要(例如,当生成的文件的存储在 FAT32 文件系统上时),将磁盘镜像拆分为多个部分(另请参阅 split(1))
# dd if=/dev/sda bs=64M status=progress | lz4 -z | split -a3 -b2G - /path/to/backup.img.lz4
如果本地磁盘空间不足,您可以通过 ssh 发送镜像
# dd if=/dev/sda 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 bs=64M status=progress
如果镜像已被分割,请改用以下命令
# cat /path/to/backup.img.lz4* | lz4 -dc | dd of=/dev/sda bs=64M 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 的功能(如在子章节“Tip:”框中的 i.e. 或 To 从句所示),无论是实际操作还是 伪代码。
有关更多替代方案,请参阅 coreutils#dd alternatives。
就地块对块修补二进制文件
在自动化 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 不必要的用户空间 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% *$%%'
管道命令之间的缓冲
在以下示例中,为避免在输出端阻塞时间超出预期时,输入端出现不必要的长时间 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 进行一次性 lseek(2) 在 shell 打开的文件描述符上,
ls -l /proc/self/fd 分配)$ 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 的用法,如 coreutils 测试套件中的示例。将可启动磁盘镜像写入块设备,可选显示进度信息
请参阅 USB flash installation medium#Using basic command line utilities 以获取通用实用程序的示例,包括在这种情况下可能最不适合的 dd。
故障排除
部分读取:复制的数据小于请求
使用 dd 创建的文件可能会因为暂时无法获得完整的输入块而小于请求的大小,根据文档
- 此外,如果未指定任何数据转换
conv操作数(即本文档中的选项),则输入将在读取后立即复制到输出,即使它小于块大小。
在 Linux 上,底层的 read(2) 系统调用在从 pipe(7) 读取时,或在读取 /dev/urandom 和 /dev/random 等设备文件时(例如,由于底层内核设备驱动程序的硬编码限制)可能会提前返回(即 部分读取)。这使得使用 bs 结合 count=n 选项时,复制数据的总大小小于预期,其中 n 限制了要复制到输出的最大(可能部分)输入块数。
dd 可能会(但不保证)警告您此类问题
dd: warning: partial read (X bytes); suggest iflag=fullblock
解决方案是按照警告操作,在 dd 命令的输入文件选项之外添加 iflag=fullblock 选项。例如,创建一个填充了随机数据的新文件,总长度为 40 MiB。
$ dd if=/dev/urandom of=new-file-filled-by-urandom.bin bs=40M count=1 iflag=fullblock
count=n 选项,建议在 dd 命令中添加 iflag=fullblock 选项,以防擦除设备的一部分或文件。从管道读取时,iflag=fullblock 的替代方案是限制 bs 为 linux/limits.h 中定义的 PIPE_BUF 常量值,以使 pipe(7) I/O 成为原子操作。例如,准备一个用随机字母数字字符串填充的文本文件,总长度为 5 MiB。
$ 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 时网络连接不佳。
- 避免写入故障介质。
参见
- dd(1p):以 manpage 形式的 dd 核心实用程序的 POSIX 规范