跳至內容

dd

出自 Arch Linux 中文维基

dd 是一個核心工具,其主要用途是複製文件,並可在複製過程中選擇性地對文件進行轉換。

cp類似,默認情況下dd會按位複製文件,但還提供了更底層的 I/O 流控制功能。

更多信息參見dd(1)完整文檔

提示:默認情況下,dd在執行複製操作過程中不會有任何輸出。要監控操作進度,可添加status=progress選項。
警告:使用dd等磁碟操作類命令時應格外小心,錯誤使用可能會不可逆轉地破壞數據。

安裝

[編輯 | 編輯原始碼]

dd是GNU coreutils的一部分。該軟體包中的其它工具,可參見核心工具

硬碟克隆與恢復

[編輯 | 編輯原始碼]

dd是一個簡單而又功能多樣的強大工具。其可逐塊從源(source)到目標(destination)複製內容,而不關心源或目標的文件系統類型、作業系統。可通過live 環境(如 Live CD)便捷的在任何計算機上使用dd

警告:作為磁碟操作類命令,使用dd時應格外小心,避免破壞其它數據。使用時必須注意輸入文件(if=)、輸出文件(of=)的順序,切勿顛倒!務必確保目標設備或分區(of=)的大於等於源(if=)的大小。

克隆分區

[編輯 | 編輯原始碼]

若要將硬碟/dev/sda上的第一個分區克隆到硬碟/dev/sdb的第一個分區上:

# 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位元組。512位元組是自20世紀80年代初以來的硬碟的「經典」塊大小。但在現代硬碟上,這並非最佳設置。建議使用64K、128K等更大的值。另外,請詳細閱讀下文的說明,該選項不僅設置「塊大小」,還影響讀取錯誤的處理方式。詳情請參閱[1][2]
  • noerror表示當遇到讀取錯誤時,忽略錯誤繼續操作。在默認情況下,dd將在遇到任何錯誤時停止。
  • sync表示當遇到讀取錯誤時,用零填充輸入塊的末尾,以保持數據偏移量同步(若認為輸入設備可能產生讀取錯誤,建議閱讀下文關於sync在遇到讀取錯誤時的行為的詳細解釋)。
  • status=progress顯示傳輸統計信息,可用於估算操作完成時間。
注意:塊大小設置會影響讀取錯誤的處理方式,詳見下文。若正在進行數據恢復,請使用ddrescue

實際上,dd存在「輸入塊大小」(IBS)和「輸出塊大小」(OBS)。

當設置bs時,將同時設置IBS和OBS。在通常情況下,若設置塊大小為1MiB,dd將從輸入文件讀取1024×1024位元組,並寫入輸出文件。但若發生讀取錯誤,則讀取會提前終止。

在使用noerror,sync選項時,一種常見的錯誤看法是,設置bs為1MiB,若讀取過程中發生錯誤,dd只會將發生錯誤的磁碟塊(例如,大小可能為512位元組)用零填充,而1MiB塊的其它部分仍然包含正確的數據;但實際上,根據文檔,dd會先嘗試從輸入文件讀取IBS個字節,一旦發生讀取錯誤,讀取就立刻停止,之後用零將輸入塊填充到OBS個字節,再寫入輸出文件。這意味著,對於磁碟而言,若在當前1MiB數據塊的頭部便發生讀取錯誤(例如,1個512位元組的扇區無法讀取),則將用零填充整個1MiB數據塊,而不是只填充發生錯誤的扇區。若用ERROR表示錯誤,則12ERROR89會變成128900000,而不是120000089。

若確定磁碟沒有錯誤,則可以使用更大的塊大小以大幅提高複製速度。例如,在一個普通的 Celeron 2.7 GHz系統上,將bs從512改為64K,複製速度從35 MB/s提高到了120 MB/s。但需要注意,若發生讀取錯誤,源磁碟上一個扇區的讀取錯誤最終會成為目標磁碟上一整個塊的錯誤,即一個512位元組的讀取錯誤可能會導致整個64 KiB的輸出塊被填充零。

注意:
  • 在克隆後,若要為ext2/3/4文件系統重新生成UUID,以確保UUID的唯一性,可對每個需要調整的分區執行tune2fs /dev/sdXY -U random。對於交換分區,需要執行mkswap -U random /dev/sdXY
  • 在克隆GPT磁碟後,可使用sgdisk來隨機化磁碟和分區的GUID,以保證其唯一性。
  • 使用dd克隆磁碟導致的分區表更改不會自動被內核註冊。要在不重啟的情況下通知內核檢查更改,可使用類似partprobeGNU Parted 的一部分)的工具。

備份分區表

[編輯 | 編輯原始碼]

參見Fdisk#備份和恢復分區表GPT_fdisk#備份和恢復分區表

創建磁碟鏡像

[編輯 | 編輯原始碼]

首先,從 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
提示:
  • 可設置塊大小(bs=)為要備份的硬碟的緩存容量。例如,若源硬碟的緩存為512 MiB,可使用bs=512M。上文使用的64 MiB塊大小的備份速度已經比默認的bs=512字節快,使用更大的塊大小可能進一步提升備份速度。
  • 上例使用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

此外,也可以從完整磁碟備份鏡像中提取MBR:

# dd if=/path/to/disk.img of=/path/to/mbr_file.img bs=512 count=1

要將備份的MBR恢復到磁碟上(注意,這將銷毀現有的分區表,導致磁碟上所有數據無法訪問):

# dd if=/path/to/mbr_file.img of=/dev/sdX bs=512 count=1
警告:若恢復的MBR與磁碟上的實際分區布局不匹配,則由於分區識別錯誤,磁碟上的數據將無法讀取,且很難修復。如果只是需要重新安裝引導加載程序,不應通過恢復MBR的方式進行,因為除了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

除磁碟操作外的額外用法

[編輯 | 編輯原始碼]

雖然dd(1)支持一些其他常用工具中不支持的獨特功能,但其與其它核心工具的命令行語法差距較大,許多默認行為並不符合預期,且在應用於特定場景時容易出錯。因此,若可能,推薦使用在特定方面更易使用的其它工具。

不過,由於dd核心工具,默認安裝於Arch和其它眾多系統上。因此,對於不便在系統上安裝新軟體包的情況,可能仍需使用dd進行操作,而不能使用其它更好的替代品。

為了同時涵蓋上述兩個方面,本節聚焦於dd(1)的獨特功能。這些功能在其它常用工具中很少見。本節的形式類似於Pacman/各軟體包管理器命令對應關係,同時給出使用dd實現與使用其它工具實現的示例,但減少了其它工具的示例的數量,以聚焦於dd的功能。其它工具的示例將在「提示」框中通過實際指令或偽代碼體現。

注意:為保持本節簡潔,只考慮官方倉庫中的替代工具,並著重展示dd具有明顯優勢的情況。如有必要,我們會詳細解釋其優勢。
更多替代方案,可參見核心工具#dd_替代品

原地逐塊修補二進制文件

[編輯 | 編輯原始碼]

在自動化的shell腳本中,經常使用dd作為二進制文件修補程序。儘管其功能相比專用工具有限,但支持:

  • 通過seek選項,在輸出文件特定偏移量處開始寫入。
  • 覆蓋輸出文件中的特定部分,但不將輸出文件截斷到寫入結束的位置(使用conv=notrunc選項)。

在下例中,使用dd修改了符合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 輸出標誌,用於以字節而不是塊為單位,指定寫入輸出文件的偏移量。
提示:若要將命令行中輸入的十六進制表示轉換為二進制字節流,可使用basenc(1) § base16printf(1)
提示:若需要在輸出文件指定偏移量處開始寫入數據,且不截斷輸出文件,除了使用dd外,還可以考慮使用支持在shell打開的文件描述符上調用lseek(2)的shell。若有以下需求:
  • 輸入文件是一個管道,其連接到一個使用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文件系統的卷標位於偏移量0x047處,長度為11位元組,卷標長度小於11位元組的,由ASCII空格填充到11位元組。要讀取存儲在鏡像文件中的VFAT文件系統的卷標:

$ 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:在開始從輸入文件read(2)之前,先定位到指定偏移量,偏移量以字節數而不是塊數指定。若輸入不支持定位操作,則跳過該輸入文件。
  • count_bytes:以字節而不是塊為單位指定從輸入文件複製的數據量。注意,即使使用了該標誌,仍可能遇到「部分讀取」問題。其實際行為類似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連接持續時間過長。此時可以在兩個命令之間「拼接」一個dddd的輸出塊大小應明顯大於輸入,同時需要小於可用內存容量:

$ curl -qgsfL http://example.org/mirrors/ftp.archlinux.org/mirrored.md5deep | dd ibs=128k obs=200M | <一个逐行对输入文件中存储的路径信息执行镜像,但不会先缓冲整个输入文件的低效镜像脚本>
警告:這絕不應被視為sponge(1)的通用替代方案,因為dd在開始複製操作之前會截斷輸出文件。

傳輸有大小限制的數據

[編輯 | 編輯原始碼]

在數據流式 shell 腳本中,可以使用dd來限制管道命令可能消耗的數據總長度。例如,以流式方式使用shell腳本函數檢查ustar頭部塊tar(5) § POSIX ustar Archives):

注意:count選項的參數中使用B後綴是GNU coreutils v9.1中新引入的功能,其效果與count_bytes輸入標誌相同。注意,在不使用B後綴時,count指定的是塊數量,而不是字節數量,即使附加了其它後綴(例如,k)。例如,count=256k表示複製262144個輸入塊而不是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。不過,若在glibc上使用GNU coreutils之外的head實現,則讀取的輸入數據量可能比指定的更多,導致在流式shell腳本中出現數據錯位問題。
提示:除上述介紹外,如果輸入文件在流式傳輸前需要lseek(2)到特定偏移量,並且dd的輸出端是一個管道,且該管道連接到的程序使用splice(2)。那麼,作為替代方案,可以考慮使用: 並使用pv(1) § S,(上文已提及)。例如,下面的示例使用bash(假設文件描述符最初未被shell分配。在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 | <一個處理大量數據但不會按需限制讀取長度,也不支持按偏移量讀取的程序>
$ exec 9<&-
注意:coreutils測試套件中的一個示例所示,上例中的xxd(1) § s可使用dd並配合count=0skip選項代替。雖然這種用法與POSIX標準和一些非GNU實現不兼容。

將可引導的磁碟鏡像寫入塊設備,並按需顯示進度信息

[編輯 | 編輯原始碼]

不推薦直接將dd用於該用途。其它常用工具參見U_盤安裝介質#使用基本命令行工具

提示:若要將文件內容寫入塊設備,並同時顯示進度信息,相比直接使用dd,建議使用dd_rescue(1) § W替代。若塊設備中已經存儲了舊版本的鏡像內容,在寫入新版本的鏡像時,其能避免不必要的寫入操作。

故障排除

[編輯 | 編輯原始碼]

部分讀取:複製的數據量小於要求的大小

[編輯 | 編輯原始碼]

dd在嘗試進行讀取操作時,一個完整的輸入塊暫時不可用,則輸出文件的最終的大小可能比要求複製的大小要小。如文檔所述

此外,如果未指定conv數據轉換操作數(即本文中提到過的選項),從輸入讀取到的內容會立即被複製到輸出,即使讀取到的數據量小於塊大小。

在Linux上,底層的read(2)系統調用在從管道(pipe(7))或某些設備文件(例如/dev/urandom/dev/random,由於底層內核設備驅動程序硬編碼的限制)讀取時,可能會在讀取到部分數據,數據量還未達到參數指定的值時便提前返回(即「部分讀取」)。這導致在結合使用bscount=n選項時,複製的數據總大小小於預期。其中count=n指定了要複製到輸出的輸入塊的數量,但每個輸入塊的大小可能小於bs字節。

在發生部分讀取時,dd可能顯示警告,但不保證一定會顯示警告:

dd: warning: partial read (X bytes); suggest iflag=fullblock

解決方案是依照警告的提示,在dd命令中添加iflag=fullblock輸入選項。例如,要創建一個大小為40MiB、填充了隨機數據的新文件:

$ dd if=/dev/urandom of=new-file-filled-by-urandom.bin bs=40M count=1 iflag=fullblock
注意:當需要從管道或特殊設備(詳見下文)中複製特定長度的一段內容時(通過count=n選項指定),強烈建議添加iflag=fullblock選項,特別是在擦除特定文件設備的一部分時。

當從管道讀取時,iflag=fullblock一個替代方案是將bs設置為linux/limits.h中定義的PIPE_BUF常量值,以使得pipe(7) I/O成為原子操作。例如,若要創建總長度為5MiB,填充了隨機字母數字字符串的文本文件:

$ 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

對於類似上述例子的情況,當要恢復中斷的傳輸時,建議以已經完整複製的輸出塊的數量為依據(125+1 records out中「+」號前面的數字,即125)。

注意:若即使添加iflag=fullblock選項後,部分I/O塊數(「+」號後面的數字仍大於1,則說明部分I/O發生了不止一次。在這種情況下,為可靠地恢復傳輸進度,建議:
  • 改用ddrescue重新進行傳輸,以更靈活地處理可能存在故障的介質上發生的部分讀取。
  • 若網絡連接不穩定且需要寫入網絡設備,使用dd_rescue(1)以直接I/O方式重新進行傳輸。
  • 避免向可能發生故障的介質寫入。

另見

[編輯 | 編輯原始碼]