xxd is the dump you can put back¶
Level: 201 · for anyone with a terminal
One line: xxd is the only dump on your machine that reverses — xxd -r turns the picture back into the file — and the column you are most tempted to edit is the one it silently throws away, which makes it the sharpest demonstration in this library that a text column is the tool talking and not the file.
Where it comes from, and why that matters¶
xxd ships with vim. That single fact explains most of the tool.
It explains the round trip: :%!xxd, edit the hex, :%!xxd -r is how you edit a binary file in a text editor, and the flags exist to make that work rather than to make a pretty dump. It explains why there is only one implementation — on macOS and on Ubuntu you are running the same program, so unlike od there is no BSD-versus-GNU split to learn (though there is a version split, at the bottom of this page). And it explains the one place it loses: it is not POSIX and not installed by default. A bare ubuntu:24.04 has od and iconv and neither xxd nor hexdump; on Debian and Ubuntu it is now its own package, apt-get install xxd.
So the practical shape is: xxd is the one to use, and od -An -tx1 is the one that will be there.
The round trip, and what survives it¶
A dump has three columns, and xxd -r reads exactly one of them.
| Column | What xxd -r does with it |
|---|---|
| the offset | an instruction. It seeks there before writing |
| the hex | the data. This is the only column that comes back |
| the text | ignored entirely. Overwrite it with anything you like |
That third row is the lesson. Every dump tool prints a right-hand column that looks like the file's contents, and every page in this library has to keep saying it is a reading rather than the thing itself. xxd settles the argument by construction: replace caf..: 1 with OVERWRITTEN, reverse the dump, and the file is unchanged. Change one digit in the hex and the byte lands.
The offset row is the one that costs people data. Because xxd -r seeks, a dump you edited by hand is only safe if you changed characters in place. Delete a line and the file does not get shorter — it gets a hole, and a hole reads back as NUL bytes at exactly the length you started with, with no complaint from anything. Section 5 below does it.
In the terminal¶
Verified output of xxd_goes_both_ways_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
1. THE DEFAULT VIEW, AND WHY IT LOOKS LIKE THAT
$ xxd s.txt
00000000: 6361 66c3 a93a 2031 e282 ac0a caf..: 1....
Offset in hex, then the bytes in PAIRS, then the same bytes as ASCII.
The pairing is cosmetic — xxd is not reading two-byte numbers the way
a bare hexdump does; 6361 is byte 63 then byte 61, in file order. -g
changes the grouping and -c the line width:
$ xxd -g 1 -c 8 s.txt
00000000: 63 61 66 c3 a9 3a 20 31 caf..: 1
00000008: e2 82 ac 0a ....
2. IT IS THE ONLY ONE HERE THAT PRINTS BITS
$ xxd -b -c 4 s.txt
00000000: 01100011 01100001 01100110 11000011 caf.
00000004: 10101001 00111010 00100000 00110001 .: 1
00000008: 11100010 10000010 10101100 00001010 ....
Eight ones and zeros per byte. od and hexdump have octal and decimal
views; neither has this one.
3. THE ONE THAT GOES BACK
$ xxd s.txt > dump.txt; xxd -r dump.txt > back.txt; cmp s.txt back.txt && echo 'identical'
identical
A dump is not just a picture here: it is a representation you can put
back. That round trip is the reason xxd ships with vim — :%!xxd, edit,
:%!xxd -r, and you have edited a binary in a text editor.
4. AND THE HALF IT THROWS AWAY
The text column is NOT the file. Overwrite it and reverse the dump:
$ sed 's/caf\.\.: 1/OVERWRITTEN/' dump.txt
00000000: 6361 66c3 a93a 2031 e282 ac0a OVERWRITTEN....
$ sed 's/caf\.\.: 1/OVERWRITTEN/' dump.txt | xxd -r
café: 1€
Nothing changed. xxd -r reads the hex and ignores everything after it,
which is the strongest demonstration in this library that the right-hand
column is the tool talking, not the file. Change a byte in the HEX and
it lands — 63 is 'c', 43 is 'C':
$ sed 's/: 6361/: 4361/' dump.txt | xxd -r
Café: 1€
5. THE OFFSET IS AN INSTRUCTION, NOT A LABEL
xxd -r SEEKS to the offset each line names. So deleting a line does not
shorten the file — it leaves a hole, and a hole is NUL bytes:
$ xxd runs.bin | sed -n '1,3p'
00000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
$ xxd runs.bin | sed '2d' | xxd -r | xxd | sed -n '1,3p'
00000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
Same length, different contents, no complaint. Line 2 came back as
zeros, because xxd -r never saw an instruction to write anything at
offset 10 and the file system gives you zeros for a gap. Editing a dump
by hand is safe as long as you change characters in place and never add
or remove a line.
6. -p: THE INTERCHANGE FORM, AND ITS INVERSE
$ xxd -p s.txt
636166c3a93a2031e282ac0a
$ xxd -p s.txt | xxd -r -p | cat
café: 1€
No offsets, no text column, no line structure — just the bytes as hex,
which is the form every other tool will take from you. The reverse is
whitespace-tolerant, so hex you pasted out of a bug report works:
$ printf '63 61 66\n65\n' | xxd -r -p
cafe
7. AUTOSKIP COLLAPSES NUL RUNS — AND ONLY NUL RUNS
$ xxd -a zeros.bin
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
*
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
$ xxd -a runs.bin | sed -n '1,3p'
00000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
64 zero bytes collapse to a *. Eighty identical 'A' bytes do not.
That is the difference from hexdump and od, whose * collapses ANY
repeated line: xxd's -a is documented as replacing nul-lines, and it
means it. It is also why -a survives a round trip — the lines it drops
are zeros, and the hole xxd -r leaves is filled with zeros:
$ xxd -a zeros.bin | xxd -r | cmp - zeros.bin && echo 'identical'
identical
8. THE TEXT COLUMN HAS A SWITCH, AND NOTHING ELSE HERE DOES
$ xxd s.txt
00000000: 6361 66c3 a93a 2031 e282 ac0a caf..: 1....
$ xxd -E s.txt
00000000: 6361 66c3 a93a 2031 e282 ac0a ./.Cz...Sb..
The same twelve bytes, twice. The hex column is identical — it is the
file. The right-hand column changed completely, because -E reads the
bytes as EBCDIC instead of ASCII, and in EBCDIC 61 is '/', c3 is 'C'
and e2 is 'S'. Neither column is more true than the other. A text
column is an AGREEMENT being applied, and this flag is the only place
in any of these tools where you get to say which agreement.
9. -i: THE FILE AS SOURCE CODE
$ xxd -i s.txt
unsigned char s_txt[] = {
0x63, 0x61, 0x66, 0xc3, 0xa9, 0x3a, 0x20, 0x31, 0xe2, 0x82, 0xac, 0x0a
};
unsigned int s_txt_len = 12;
The variable name comes from the filename. -i is how a font, an icon or
a test fixture gets compiled into a C program with no file to ship.
10. WHAT IS NOT HERE
-e prints little-endian GROUPS — 63 61 66 c3 as c3666163, which is what
a bare hexdump does to every pair without being asked. It is not in this
run because the two xxd versions pad its short last group by different
amounts; the page shows both, dated. Reach for it when you are reading
32-bit values, not when you want to see the file.
The text column has a switch¶
Section 8 is the part that belongs to this library rather than to a tools manual. xxd -E prints the same bytes with the right-hand column read as EBCDIC instead of ASCII, and the two columns disagree completely: the bytes that spell café: 1€ in UTF-8 contain / and C and S under IBM's agreement, because 61 is / there and c3 is C.
Nothing else in the dump changed, because nothing else could — the hex column is the file. What changed is the agreement being applied to it, and -E is the only flag in any of these four tools that lets you say which agreement you want. That is the whole of a character is a number in one command line, and it is not a museum piece: EBCDIC is what a mainframe interface will hand you, and a file that arrives from one looks exactly like this — plausible hex, and a text column full of punctuation.
What only xxd does¶
The hexdump page has the full four-tool comparison table; this is the short version of what is on this side of it.
-r— reverse. No other dump here goes backwards.xxd -r -ptakes a bare hex string, tolerates whatever whitespace you pasted with it, and writes the bytes.-b— bits. Eight ones and zeros per byte.odandhexdumphave octal and decimal views and neither has this one.-i— a C array. The file as source code, named after itself, which is how an icon or a test fixture gets compiled in.-E— EBCDIC. The text column, under different management.- An honest default. Bare
xxdshows the bytes in file order. Barehexdumpswaps every pair and bareodprints octal words at octal offsets; both are one flag away from being right, andxxdneeds no flag at all. -acollapses NUL runs, and only NUL runs.hexdumpandodcollapse any repeated line by default, which is whyhexdump -e '1/1 "%.2x"'can emit four characters for a 64-byte file.xxdnever collapses unless asked, and what it collapses when asked is the one thingxxd -rreconstructs for free — so-asurvives the round trip, which is not a coincidence.
The version split, which is the one thing to watch¶
There is no BSD/GNU split here, but there is a version one, and it has the same consequence for anything that compares output:
$ xxd -e s.txt # macOS, xxd 2025-11-26
00000000: c3666163 31203aa9 0aac82e2 caf..: 1....
$ xxd -e s.txt # ubuntu:24.04, xxd 2023-10-25
00000000: c3666163 31203aa9 0aac82e2 caf..: 1....
One space. xxd -e prints little-endian groups — 63 61 66 c3 as c3666163, which is what a bare hexdump does to every pair without being asked — and the two versions pad its short final group differently. Everything else in the example above is byte-identical on both machines.
Two things follow. Reach for -e when you are reading 32-bit values, not when you want to see the file; the whole difference between it and hexdump's default is that here the reordering has a name and you asked for it. And do not diff two dumps taken on two machines unless you know they came from the same xxd — the bytes will agree and the whitespace will not, which is the least interesting way to lose an afternoon.
Which flags are worth remembering¶
| Flag | For |
|---|---|
-r |
put the dump back. With -p, put a bare hex string back |
-p |
just the hex, no offsets and no text — the form other tools accept |
-c N, -g N |
bytes per line, bytes per group. -g 1 unpairs the default |
-s, -l |
skip to an offset, stop after a length. -s takes +/- for relative |
-u |
uppercase hex, when you are comparing against a spec that uses it |
-b |
bits |
-i |
a C array |
-E |
read the text column as EBCDIC |
-a |
collapse runs of NUL |
If you are coming from Python or ABAP¶
Python. bytes.hex() is xxd -p and bytes.fromhex() is xxd -r -p, and unlike the shell pair they are exact inverses with no whitespace or offset semantics in between — bytes.fromhex() accepts spaces and rejects everything else. The habit worth taking from this page is the round trip itself: when you are unsure whether a transformation preserved a file, do not compare what the two files look like, compare open(p,'rb').read() — which is what cmp is doing in section 3, and what the text column can never tell you.
ABAP (Not machine-checked — CI cannot run ABAP.) -E is the flag that will matter to you, because EBCDIC is not history on a mainframe interface. The ABAP shape of section 8 is cl_abap_conv_codepage: the same xstring converted with two different code pages gives two different strings, both of them well-formed, and only one of them is what the sender meant. Ask the sender which code page, and put the answer in the interface specification — do not infer it from which conversion produced fewer question marks. Verify any specific code-page number against the system rather than trusting a number quoted in documentation, this page included.
Try it¶
Then:
xxd yourfile > d.hex, edit a byte in the hex column with your editor,xxd -r d.hex > new, andcmp yourfile newto see exactly where it landed.- Do it again, but edit the text column instead. Nothing happens. That is the point.
xxd yourfile | sed '3d' | xxd -r | xxd | head -4— the length is the same and line 3 is zeros.xxd -Ea file that came from a mainframe, if you have one. Theniconvit and dump it again.- In vim:
:%!xxd, change something,:%!xxd -r,:w.
Practice¶
Edit the dump and put it back. Dump a file with xxd, change cafe to XXXX in the text column, and run xxd -r. Predict the result before you do it.
Then three more: edit the hex column instead and predict that; feed xxd -r a dump containing only the single line 00000004: 21 and say how many bytes come out and what the first four are; and say why xxd -p is the form to paste into a bug report.
Answers
Verified output of xxd_kata_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
THE ROUND TRIP
original 636166650a
00000000: 6361 6665 0a cafe.
xxd -r back 636166650a identical: yes
NOW EDIT THE TEXT COLUMN AND PUT IT BACK
00000000: 6361 6665 0a XXXX.
result 636166650a -> cafe
Nothing changed. xxd -r reads the OFFSET and the HEX and stops; the
text column is output only, and editing it edits nothing. That is the
sharpest demonstration in this library that the right-hand column is
the tool talking, not the file.
NOW EDIT THE HEX COLUMN
6665 -> 7065 636170650a -> cape
That is the column that is the file.
THE OFFSET IS AN INSTRUCTION, NOT A LABEL
a dump with ONE line at offset 4 -> 5 bytes: 0000000021
xxd -r SEEKS to the offset, so the four bytes before it are whatever
the file already had -- here nothing, so they are NUL. Delete a line
from a dump and you do not shorten the file; you punch a hole in it.
AND THE ONE FLAG THAT CHANGES THE CONTRACT
xxd -p | xxd -r -p 636166650a identical: yes
-p has no offsets and no text column, so it is pure hex both ways --
which makes it the form to paste into a bug report and the form to
pipe. The full dump is for reading; -p is for round-tripping.
See also¶
hexdumpis a format engine wearing six presets — the four-tool comparison table, and the tool with the opposite default- Reading a hex dump — the three columns, if this page assumed one you have not met
- Grouping is a choice — what
-gis claiming about the file, and why 5 and 6 are widths no hex dump can show - Inspecting a file — these tools inside a workflow, and which column is the file
- A character is a number — the agreement
-Eswaps out odreads types, not bytes — the one that will be installed whenxxdis not- Code pages — the family EBCDIC belongs to, and why more than one of them exists