Skip to content

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 -p takes a bare hex string, tolerates whatever whitespace you pasted with it, and writes the bytes.
  • -b — bits. Eight ones and zeros per byte. od and hexdump have 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 xxd shows the bytes in file order. Bare hexdump swaps every pair and bare od prints octal words at octal offsets; both are one flag away from being right, and xxd needs no flag at all.
  • -a collapses NUL runs, and only NUL runs. hexdump and od collapse any repeated line by default, which is why hexdump -e '1/1 "%.2x"' can emit four characters for a 64-byte file. xxd never collapses unless asked, and what it collapses when asked is the one thing xxd -r reconstructs for free — so -a survives 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:

Measured 2026-09-07 — same file, same flag, two xxd versions. Verbatim; not machine-checked, which is why this flag is not in the run above.
$ 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 groups63 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

cd 11_Tools/xxd/examples
bash xxd_goes_both_ways_sh.sh

Then:

  1. xxd yourfile > d.hex, edit a byte in the hex column with your editor, xxd -r d.hex > new, and cmp yourfile new to see exactly where it landed.
  2. Do it again, but edit the text column instead. Nothing happens. That is the point.
  3. xxd yourfile | sed '3d' | xxd -r | xxd | head -4 — the length is the same and line 3 is zeros.
  4. xxd -E a file that came from a mainframe, if you have one. Then iconv it and dump it again.
  5. 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