跳转至内容

dd

来自 ArchWiki

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因:将笔记和警告放在提示中是荒谬的。(在 Talk:Dd 中讨论)

dd 是一个 核心实用程序,其主要目的是复制文件并在复制过程中可选地进行转换。

cp 类似,默认情况下 dd 会进行文件的逐位复制,但具有更低级别的 I/O 流控制功能。

有关更多信息,请参阅 dd(1)完整文档

提示 默认情况下,dd 在任务完成前不会输出任何内容。要监控操作进度,请在命令中添加 status=progress 选项。
警告 使用 dd 时应极其谨慎,因为它与任何此类命令一样,可能会不可逆地销毁数据。

安装

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 显示周期性的传输统计信息,可用于估算操作何时可能完成。
警告 不要在有读取错误(read errors)的设备上使用 dd。如果 dd 检测到读取错误,它将默认失败。不要使用 convnoerror 和/或 sync 选项来覆盖此行为。相反,请使用 ddrescue 进行数据恢复。
  • 要重新获得 ext2/3/4 文件系统的唯一 UUID,请在每个分区上使用 tune2fs /dev/sdXY -U random。对于交换分区,请使用 mkswap -U random /dev/sdXY
  • 如果您正在克隆 GPT 磁盘,您可以使用 sgdisk 来随机化磁盘和分区 GUID,并重新获得它们的唯一性。
  • dd 对分区表的更改不会被内核注册。要不重启即通知更改,请使用 partprobeGNU Parted 的一部分)之类的实用程序。

备份分区表

请参阅 fdisk#Backup and restore partition tablegdisk#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
提示
  • 您可能希望使用与您正在备份的 HDD 缓存量相等的块大小(bs=)。例如,bs=512M 适用于 512 MiB 缓存。本文中提到的 64 MiB 比默认的 bs=512 字节要好,但使用更大的 bs= 可能会更快。
  • 以上示例使用了 lz4(1),它使用多核进行压缩,但您可以将其替换为 任何其他压缩程序

恢复系统

恢复您的系统

# 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 个部分组成

  1. 前 440 字节包含引导代码(引导加载程序)。
  2. 接下来的 6 字节包含磁盘签名。
  3. 接下来的 64 字节包含分区表(4 个条目,每个条目 16 字节,每个主分区一个条目)。
  4. 最后 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 会使您的数据不可读,并且几乎不可能恢复。如果您只需要重新安装引导加载程序,请参阅其各自的页面,因为它们也采用了 DOS 兼容区域GRUBSyslinux

如果您只想恢复引导加载程序,但不恢复主分区表条目,只需恢复 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

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因:本节中的一些段落难以阅读;需要校对。(在 Talk:Dd 中讨论)

正如一些读者可能已经意识到的那样,dd(1) 核心实用程序的命令行语法与其他实用程序不同。此外,虽然支持 其他通用实用程序中找不到的一些独特功能,但它的几个默认行为在应用于特定场景时要么不太理想,要么容易出错。因此,用户可能希望使用在某些方面更好的替代方案来代替 dd 核心实用程序。

话虽如此,但仍然值得注意的是,由于 dd 是一个核心实用程序,它默认安装在 Arch 和许多其他系统上,如果要在系统上安装新软件包不方便,它比一些替代方案或更专业的实用程序更受欢迎。

为了涵盖上述两个方面,本节专门用于总结 dd(1) 核心实用程序中在其他通用实用程序中很少见到的功能——其形式类似于 pacman/Rosetta 文章,但示例数量减少到只检查 dd 的功能(如在子章节“Tip:”框中的 i.e.To 从句所示),无论是实际操作还是 伪代码

注意 为了保持本节的简洁性,替代实用程序的比较仅包括官方存储库中的那些。我们检查 dd 相对于它们的明显优势的情况。如有必要,我们将详细解释优势。
有关更多替代方案,请参阅 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) 到输出之前,以字节偏移量而不是块偏移量查找输出。
提示 要从命令行输入十六进制表示的字节流,请使用 basenc(1) § base16 和/或 printf(1)
提示 在此功能网格中( write(2) 带偏移量,不截断),代替使用 dd,如果您遇到以下情况,可以考虑使用支持在 shell 打开的文件描述符上调用 lseek(2) 的 shell
  • dd 的输入文件是一个连接着使用了 splice(2) 系统调用的程序的管道,并且用户希望避免 dd 不必要的用户空间 I/O 以获得更好的性能
  • 或者,为了避免在 shell 脚本循环中频繁 fork(2) 以减少性能损失
那么,首先需要让该 shell 打开文件描述符,并在文件描述符上执行一些查找操作,以便将此文件描述符分配给使用 splice(2) 系统调用的相应实用程序的输出端(或不进行 fork 的 shell 内建命令,如下例的 zshmodules(1) § sysseek
$ 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}>&-
警告 如果您不确定需要用偏移量写入的程序是否真的使用了 splice(2),请避免使用此方法,这意味着该程序不应对其输出执行任何查找或截断操作。某些程序可能会自行查找/截断输入/输出文件描述符,即使这些行为在命令行标志中未指定,这也会使您 shell 的 lseek(3) 调用失效,或意外地截断您打开的文件描述符。

打印 VFAT 文件系统镜像的卷标

提示 对于这个特定的场景,一个更实际的选择是 file

读取 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% *$%%'
注意 这两个输入标志目前都未被文档化
  • 前者 skip_bytes 指示 dd 在开始 read(2) 之前,以字节数量而非块数量来查找(或跳过,如果输入不可查找)输入文件。
  • 后者 count_bytes 允许用户以字节为单位指定要从输入文件中复制的块的总数,而不是块的数量。这可能会令人困惑,因为指定此选项仍可能受到部分 read(2) 的影响,将其视为输入块 count 的分数,以便更好地理解此行为。
提示 在给定长度内将数据从输入(带偏移量)传输到输出,在 shell 脚本中,您还可以考虑使用 curl(1) § r, 作为使用范围表示法的通用替代方案。
注意 curl 在输入文件是设备/管道时不支持查找/跳过,另一个替代方案 socat(1) 支持这些在输入文件(包括块设备,但不包括管道和字符设备)上的操作,但不如 curl 通用。
$ socat -u -,seek=$((0x047)),readbytes=11 - < empty-hole.img | sed -e 's% *$%%'

管道命令之间的缓冲

提示 如标题中所述,对于此特定场景,一个实用的选择是 sponge(1),它通过先写入 $TMPDIR(如果未设置,则为 /tmp)来支持原子写入。

在以下示例中,为避免在输出端阻塞时间超出预期时,输入端出现不必要的长时间 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
警告 它绝不能被视为 sponge(1) 的通用替代方案,因为 dd 在开始整个复制操作之前会截断输出文件。

限制大小传输数据

在数据流 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
提示 要将数据从输入(带偏移量)流式传输到输出并在给定长度内,另一种选择是 pv(1) § S, 它支持 splice(2) 系统调用。
注意 另一个候选替代方案是 head(1) § c,尽管 GNU coreutils 和 glibc 以外的实现 可能会消耗比请求更多的数据,导致流式 shell 脚本中的数据对齐问题。
提示 除了上述功能网格外,如果输入文件需要在流式传输前通过特定偏移量 lseek(2),并且 dd 的输出端是一个连接着使用 splice(2) 的程序,那么,作为替代方案,可以考虑使用以及 pv(1) § S,上面提到)如下面的 bash 示例(假设文件描述符尚未由 bash 中的 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<&-
注意 虽然不兼容 POSIX 和一些非 GNU 实现,但在上面的示例中,可以使用 dd 结合 count=0skip 选项来替换 xxd(1) § s 的用法,如 coreutils 测试套件中的示例

将可启动磁盘镜像写入块设备,可选显示进度信息

请参阅 USB flash installation medium#Using basic command line utilities 以获取通用实用程序的示例,包括在这种情况下可能最不适合的 dd

提示 要将文件内容写入块设备(带进度指示器),建议的替代方案是 dd_rescue(1) § W。它能够在覆盖设备上的旧版本镜像时避免不必要的写入。

故障排除

部分读取:复制的数据小于请求

使用 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替代方案是限制 bslinux/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

由于输出文件不是管道,您可以选择使用 ibsobs 选项分别为(输入)管道和(输出)磁盘上的文件单独设置块大小。例如,为输出文件设置更高效的块大小。

$ 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 规范