dd

出自 ArchWiki

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=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 显示周期性的传输统计信息,可用于估计操作何时可能完成。
注意: 您指定的块大小会影响读取错误的处理方式。请阅读下文。对于数据恢复,请使用 ddrescue

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 的分区表更改不会被内核注册。要在不重启的情况下通知更改,请使用类似 partprobeGNU 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
提示
  • 您可能希望使用等于您要备份的 HDD 上的缓存量的块大小 (bs=)。例如,bs=512M 适用于 512 MiB 缓存。本文中提到的 64 MiB 比默认的 bs=512 字节更好,但使用更大的 bs= 可能会运行得更快。
  • 以上示例使用 lz4(1),它使用多核进行压缩,但您可以将其替换为任何其他压缩程序

恢复系统

要恢复您的系统

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

  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 的特性(如子章节“提示:”框中 i.e.To 子句所示),无论是实践还是伪代码

注意: 为了保持本节简洁,替代实用程序的比较仅包括在官方存储库中找到的那些。我们检查 dd 具有明显优势的情况。如有必要,我们将详细解释优势。
有关更多替代方案,请参阅 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) 到输出之前,以字节偏移量而不是块偏移量来查找输出。
提示: 要从命令行输入十六进制表示法打印字节流,请使用 basenc(1) § base16 和/或 printf(1)
提示: 在此功能网格中( write(2) 带有偏移量,且不截断),除了使用 dd 之外,如果以下情况,可以考虑使用支持调用 lseek(2) 的 shell 在 shell 打开的文件描述符上
  • dd 的输入文件是与利用 splice(2) 系统调用的程序连接的管道,并且用户希望避免 dd 不必要的 userspace I/O 以获得更好的性能
  • 或者,为了避免在 shell 脚本循环中频繁 fork(2) 以减少性能损失
那么,有必要让该 shell 首先打开文件描述符,并在文件描述符上执行一些查找操作,以将此文件描述符分配为使用 splice(2) 系统调用的相应实用程序的输出端(或者 shell 内置命令不 fork,如以下 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

提示: 正如标题中已经指出的那样,对于此特定场景,一个实用的选择是 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
警告: dd 在开始整个复制操作之前会截断输出文件,因此绝不应将其视为 sponge(1) 的通用替代方案。

传输大小受限的数据

在数据流 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 示例所示(假设 shell 中尚未通过 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<&-
注意: 虽然与 POSIX 和某些非 GNU 实现不兼容,但在 coreutils 测试套件中的示例 中,可以使用 dd 结合 count=0skip 选项来替换上述示例中 xxd(1) § s 的用法。

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

有关包含潜在最不适合该情况的 dd 的通用实用程序的示例,请参阅 USB 闪存安装介质#使用基本命令行工具

提示: 要将文件内容写入块设备(带有进度指示器),建议的替代方案是 dd_rescue(1) § W。在用较新版本的镜像覆盖设备上的旧版本镜像的情况下,它可以避免不必要的写入。

故障排除

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

如果当前没有可用的完整输入块,则使用 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

由于输出文件不是管道,因此可以首选使用 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。
  • 避免写入故障介质。

参见