dd
Copy blocks, with control over size and position
dd copies bytes from one place to another in fixed-size blocks, and its interest is the control:
where in the input to start, where in the output to write, how much, and whether to truncate what
was there. cp can do none of that, which is why dd survives despite a syntax that belongs to a
different decade.
The syntax is its own: if= and of= rather than positional arguments, no dashes, and no spaces
around the equals. Everything unrecognised is a silent no-op, so dd if=disk.img of=/dev/sdb bs 1M
runs with the default 512-byte block and finishes an hour later than expected.
The of= argument is the one that destroys things. dd writes wherever it is pointed, to a
device as readily as to a file, and gets /dev/sdb and /dev/sdc confused about as often as
anyone does. Run lsblk or blkid on the target immediately
before, in the same terminal, so what you read is what you type.
Block size is a throughput setting and nothing else: bs=1M moves the same bytes as the default
bs=512 in a fraction of the calls. Where bs does change the result is with count, skip and
seek, which are all counted in blocks rather than bytes, so count=5 means five of whatever bs
says.
By default dd truncates the output file. conv=notrunc is what lets it write into the middle of
one, and leaving it out is how a patch of a few bytes turns into a file three bytes long.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
two disk images data.img holds a 32M ext4 labelled tipsdata, and blank.img is 16M of zeros. Examples that make a file of their own write it into the page's working directory, which is emptied before the next one runs. The same pair backs mkfs, mount, blkid and fsck.
total 20912
-rw-r--r-- 1 root root 16777216 Jun 15 10:00 blank.img
-rw-r--r-- 1 root root 33554432 Jun 15 10:00 data.img
Making a file of a given size
bs is the block size and count is how many blocks, so the size is the two multiplied. status=none silences the summary, which is worth having in a script and worth leaving on when you want to see what happened.
Make an 8M file of zeros
dd if=/dev/zero of=disk.img bs=1M count=8
The summary goes to standard error, which is why it appears even when the output is redirected. records in and records out count blocks rather than bytes, and a mismatch between the two means something was short-read.
Show output
Your output will differ: the elapsed time and the transfer rate are this machine's
8+0 records in
8+0 records out
8388608 bytes (8.4 MB, 8.0 MiB) copied, 0.002727 s, 3.1 GB/s
Make the same file without the commentary
dd if=/dev/zero of=disk.img bs=1M count=8 status=none
stat -c "%s bytes" disk.img
status=none drops the summary entirely. The alternative, status=progress, replaces it with a running count that updates in place, which is what you want on a copy that takes minutes.
Show output
8388608 bytes
Use a smaller block size
dd if=/dev/zero of=disk.img bs=512 count=16 status=none
stat -c "%s bytes" disk.img
Sixteen blocks of 512 bytes rather than eight of a megabyte. count is always in blocks, so changing bs and leaving count alone changes the size of the result.
Show output
8192 bytes
Fill a file with random bytes
dd if=/dev/urandom of=r.bin bs=1k count=4 status=none
stat -c "%s bytes" r.bin
/dev/urandom is the source for a file that has to be unguessable rather than merely present. It is slower than /dev/zero by orders of magnitude, which shows up on anything larger than this.
Show output
4096 bytes
Make a large file that occupies nothing
dd if=/dev/zero of=sparse.img bs=1M count=0 seek=64 status=none
echo "apparent: $(stat -c %s sparse.img)"
echo "allocated: $(du -h sparse.img | cut -f1)"
count=0 writes no data and seek=64 moves the offset first, so the file ends 64M in with nothing behind it. du reports what is allocated and stat reports the length, and a sparse file is where the two disagree.
Show output
apparent: 67108864
allocated: 0
Flush to the device before reporting success
dd if=/dev/zero of=disk.img bs=1M count=4 conv=fsync
Without conv=fsync the copy is finished when the kernel has the data, not when the disk does, so the rate dd reports is partly a measure of how much memory was free. Add it before drawing conclusions from the number, and before unplugging anything.
Show output
Your output will differ: the elapsed time and the transfer rate are this machine's
4+0 records in
4+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.00781867 s, 536 MB/s
Position within the file
skip moves the read forward in the input, seek moves the write forward in the output, and both count in blocks of bs. Setting bs=1 makes them byte offsets, at the cost of a system call per byte.
Copy a file whole
printf 'hello world\n' > src.txt
dd if=src.txt of=dst.txt status=none
cat dst.txt
With no bs, count, skip or seek this is a slow cp with a stranger syntax. Everything below is what makes it worth using instead.
Show output
hello world
Take the first few bytes
printf '0123456789' > src.txt
dd if=src.txt of=part.txt bs=1 count=5 status=none
cat part.txt; echo
bs=1 count=5 is five bytes from the start. head -c 5 does the same thing faster, and dd is only the better tool here once skip or seek is involved.
Show output
01234
Start partway into the input
printf '0123456789' > src.txt
dd if=src.txt of=part.txt bs=1 skip=5 status=none
cat part.txt; echo
skip=5 discards the first five bytes and copies the rest. This is how a partition is pulled out of a whole-disk image, with skip set to where the partition starts.
Show output
56789
Take a slice from the middle
printf '0123456789' > src.txt
dd if=src.txt bs=1 skip=3 count=4 status=none
echo
skip and count together, and no of= at all, so the result goes to standard output where a pipe can take it. Four bytes starting at the fourth.
Show output
3456
Take a slice out of a disk image
dd if=/srv/images/data.img of=part.img bs=1M skip=1 count=2 status=none
stat -c "%s bytes" part.img
A two-megabyte slice, starting one megabyte in. The same arithmetic pulls a partition out of a whole-disk image, once fdisk -l has said where that partition starts and how long it is.
Show output
2097152 bytes
Overwrite bytes in place
printf 'AAAA' > f.txt
printf 'BB' | dd of=f.txt bs=1 seek=1 conv=notrunc status=none
echo "result: $(cat f.txt)"
seek=1 starts writing one byte in and conv=notrunc keeps what follows. The input comes from a pipe, so there is no if=.
Show output
result: ABBA
Watch what happens without conv=notrunc
printf 'AAAA' > f.txt
printf 'BB' | dd of=f.txt bs=1 seek=1 status=none
echo "result: $(cat f.txt)"
echo "size: $(stat -c %s f.txt)"
The same command without conv=notrunc: dd truncates the output file first, so the tail is gone and four bytes have become three. There is no warning and no way back.
Show output
result: ABB
size: 3
Reading from a pipe
A read from a pipe returns what is available rather than what was asked for, and dd counts a short read as a whole block. This is the trap that makes a piped dd quietly copy a fraction of what it was told to.
Watch a piped read come up short
head -c 3000000 /dev/zero | dd of=short.img bs=1M count=2 2>&1 >/dev/null | grep records
0+2 means zero full blocks and two partial ones: dd asked for a megabyte twice, got whatever the pipe had ready, and counted each as one of its two blocks. The file it wrote is a fraction of two megabytes and a different fraction on every run.
Show output
0+2 records in
0+2 records out
Make it wait for whole blocks
head -c 3000000 /dev/zero | dd of=full.img bs=1M count=2 iflag=fullblock 2>&1 >/dev/null | grep records
stat -c "%s bytes" full.img
iflag=fullblock keeps reading until the block is full or the input ends, and 2+0 says both blocks were whole. Give it to any dd whose input is a pipe, a socket or a slow device.
Show output
2+0 records in
2+0 records out
2097152 bytes
Pad a short block instead
printf 'abc' | dd of=padded.bin bs=8 count=1 conv=sync status=none
stat -c "%s bytes" padded.bin
od -c padded.bin | head -1
conv=sync pads each short block out to bs with NUL bytes rather than writing it short. That is what a fixed-record format wants and what a file copy never does.
Show output
8 bytes
0000000 a b c \0 \0 \0 \0 \0
Whole devices and images
The job dd is still kept around for. An image of a device is a byte-for-byte copy including the filesystem's own structures, so it restores to a working disk, and so pointing it at the wrong one leaves nothing to recover.
Copy an image byte for byte
dd if=/srv/images/data.img of=copy.img bs=1M status=none
cmp /srv/images/data.img copy.img && echo identical
cmp afterwards rather than trust: a copy that reported success and differs is the case worth catching, and it costs one command. bs=1M is what keeps a copy of any size from taking all day.
Show output
identical
Make an image and put a filesystem on it
dd if=/dev/zero of=disk.img bs=1M count=8 status=none
loop=$(losetup --find --show disk.img)
mkfs.ext4 -q -L made "$loop"
mount "$loop" /mnt/backup
df -h /mnt/backup | tail -1
The whole sequence: dd makes the space, mkfs makes the filesystem, mount attaches it. 6.5M usable out of 8M, because the journal and the inode tables were spent first.
Show output
Your output will differ: the loop device number depends on what else on this machine has one attached
/dev/loop0 6.5M 46K 5.9M 1% /mnt/backup
Keep the holes when copying a sparse image
dd if=/dev/zero of=big.img bs=1M count=8 status=none
dd if=big.img of=copy.img bs=1M conv=sparse status=none
echo "apparent: $(stat -c %s copy.img)"
echo "allocated: $(du -h copy.img | cut -f1)"
conv=sparse skips writing a block that is all zeros, so the copy takes no space at all while reporting the same length. Wrong for a device, where the blocks that go unwritten keep whatever was on them.
Show output
apparent: 8388608
allocated: 0
Read a device without writing anything
dd if=/srv/images/data.img of=/dev/null bs=1M status=none
echo "read ok: $?"
Writing to /dev/null reads the whole device and discards it, which is a way to find out whether every block is readable. A failing disk reports an I/O error part way through rather than at the end.
Show output
read ok: 0
Send the summary somewhere separate
dd if=/dev/zero bs=1M count=4 2>&1 >/dev/null | tail -1
dd writes its data to standard output and its summary to standard error, so the two can be separated. Here the data is discarded and the summary kept, which is the form for timing a read without storing it.
Show output
Your output will differ: the elapsed time and the transfer rate are this machine's
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.000507625 s, 8.3 GB/s