od reads types, not bytes¶
Level: 201 · for anyone with a terminal
One line: od's interface is a C type — -t x1 means hexadecimal, one byte at a time — which explains its default of 16-bit octal words at octal offsets, lets it print one file three ways in a single pass, and makes it the only dump here whose output you cannot quote without naming the machine it came from.
The one that will be there¶
Of the four tools in this chapter's dump table, od is the one you do not get to choose. It is POSIX, it is part of coreutils, and on a bare ubuntu:24.04 it is present when xxd, hexdump, file and hexyl are all absent. Rescue shells, minimal containers, a stripped appliance, somebody else's build image — od is what is in the room.
That is the entire practical case for learning it, and it is enough. It is also slightly unfortunate, because od has the least helpful defaults and the least portable output of the three, and its two most inviting flags are the two you should not trust. This page is the short route to the parts that are solid.
The type is the interface¶
hexdump takes a format string and xxd takes a layout. od takes a type, and once you see that, everything else about the tool follows from it.
A type is a letter and a size. The letter says how to print a value; the size says how many bytes one value is.
| Letter | Prints | Size | Bytes | |
|---|---|---|---|---|
x |
hexadecimal | 1 2 4 8 |
that many | |
o |
octal | C |
a char |
|
d |
signed decimal | S |
a short |
|
u |
unsigned decimal | I |
an int |
|
f |
floating point | L |
a long |
|
a |
the byte's ASCII name | |||
c |
the byte as a character |
So -t x1 is hex bytes, -t d2 is signed 16-bit integers, -t u1 is unsigned bytes in decimal, -t f8 is doubles, and -t dI is signed ints at whatever width this compiler calls an int. od will read your CSV as a run of doubles if you ask it to, and it is not being perverse when it does — it is doing what a C cast does, which is what the tool was built for in the first place.
Two things fall out of that immediately.
The default is a type too. Bare od is -t o2: sixteen-bit words, printed in octal. And the offset column is octal as well, so a twelve-byte file ends at 0000014. Two number bases, neither of them announced, in the tool you reach for when there is nothing else — it is the same shape as hexdump's swapped pairs, one base further from what you wanted. Both defaults come from a machine where the 16-bit word, not the byte, was the unit anyone cared about.
Sizes above 1 are a claim about the file. -t x2 prints 63 61 as 6163 and -t x4 prints 63 61 66 c3 as c3666163, because a multi-byte value has an end that comes first and this machine puts the low byte there. That is the byte-order question again, and it is correct behaviour: you asked for numbers. When you want the file, ask for bytes — -t x1 — and the question cannot arise.
In the terminal¶
Verified output of od_reads_types_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
1. TWO NUMBER BASES NOBODY ASKED FOR
$ od s.txt | tidy
0000000 060543 141546 035251 030440 101342 005254
0000014
Twelve bytes, and not one of them is on screen. The default type is
-t o2: SIX 16-bit words, printed in octal. And the offsets are octal
too, so the last line says 0000014 for a 12-byte file — 14 octal is 12.
Two number bases, neither of them stated, in the output of the only
dump tool a stripped-down machine is guaranteed to have.
2. THE INCANTATION
$ od -An -tx1 s.txt | tidy
63 61 66 c3 a9 3a 20 31 e2 82 ac 0a
-A n drops the offset column, -t x1 says hexadecimal one byte at a
time. Those twelve numbers ARE the file. If you remember one od
command, remember this one.
3. -A PICKS THE RADIX OF THE OFFSET COLUMN
$ od -Ad -tx1 s.txt | tidy
0000000 63 61 66 c3 a9 3a 20 31 e2 82 ac 0a
0000012
$ od -Ao -tx1 s.txt | tidy
0000000 63 61 66 c3 a9 3a 20 31 e2 82 ac 0a
0000014
d for decimal, o for octal (the default), n for none. There is an x
for hex too, and it is the one thing on this page that is not the same
width on both machines — the page has that measurement.
4. -t IS A TYPE: A LETTER AND A SIZE
The letter is how to print it, the number is how many bytes that is.
$ od -An -tx1 s.txt | tidy
63 61 66 c3 a9 3a 20 31 e2 82 ac 0a
$ od -An -tu1 s.txt | tidy
99 97 102 195 169 58 32 49 226 130 172 10
$ od -An -tx2 s.txt | tidy
6163 c366 3aa9 3120 82e2 0aac
$ od -An -tx4 s.txt | tidy
c3666163 31203aa9 0aac82e2
Same twelve bytes, four readings. x1 and u1 are the same bytes in two
bases. x2 and x4 group them into 16- and 32-bit numbers and print each
number in THIS CPU's byte order, which is why 63 61 becomes 6163 and
63 61 66 c3 becomes c3666163. Grouping is a claim about the file.
5. AND IT WILL READ YOUR TEXT AS ANYTHING YOU NAME
$ od -An -td2 s.txt | tidy
24931 -15514 15017 12576 -32030 2732
The same bytes as SIGNED 16-bit integers. c366 is 50022 unsigned and
-15514 signed, and od printed the second one because you asked for d.
$ od -An -tdI s.txt | tidy
-1016700573 824195753 179077858
Sizes have letters as well as numbers: C is a char, S a short, I an
int, L a long. -t dI is three signed ints, because twelve bytes is
three of them. There is -t f4 and -t f8 for floating point as well,
and od will happily tell you what your CSV is as a run of doubles.
Nothing here is a bug: od is doing exactly what a C cast does, which
is what the tool was built for.
6. SEVERAL READINGS IN ONE PASS — THE THING ONLY od DOES
$ od -An -tx1 -tc nospace.txt | cols
63 61 66 c3 a9 3d 31 e2 82 ac 0a
c a f 303 251 = 1 342 202 254 \n
Two -t flags, two rows for every line of input, each byte's hex sitting
directly above what it is as a character. hexdump needs three -e
strings to line those up and xxd cannot do it at all. Add a third -t
and you get a third row:
$ od -An -tx1 -tu1 -tc nospace.txt | cols
63 61 66 c3 a9 3d 31 e2 82 ac 0a
99 97 102 195 169 61 49 226 130 172 10
c a f 303 251 = 1 342 202 254 \n
(These two use '=' where the demo file has ': ' — under -t c a space
byte prints as blanks, which the column helper cannot tell apart from
padding. That is a limit of the helper, not of od.)
7. A WINDOW INTO A BIG FILE
$ od -Ad -j 2 -N 4 -tx1 s.txt | tidy
0000002 66 c3 a9 3a
0000006
-j skips, -N stops. The offsets keep counting from the front of the
file, so what you read is the address in the file and not in the
window — which is what you want when you are quoting it to someone.
8. THE STAR, WHICH od SHARES WITH hexdump
$ od -Ad -tx1 zeros.bin | tidy
0000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000064
64 bytes in, one line out. A * means the line above repeats. -v turns
it off, and anything parsing od's output needs -v:
$ od -Ad -v -tx1 zeros.bin | tidy | wc -l | tr -d ' '
5
^ five: four lines of sixteen bytes, plus the final offset line.
9. WHY EVERY BLOCK ABOVE ENDS IN | tidy
Because the raw output cannot be recorded. BSD od indents, pads a short
line out to the width a full 16-byte line would take, and adds a
trailing blank line; GNU od does none of that, and its own indent is
not constant between formats either. The NUMBERS agree on both
machines. Nothing else does — so od output is the one thing in this
chapter you cannot quote without saying which machine it came from,
and the page shows both raw versions side by side rather than this
script pretending there is one answer.
The thing only od does¶
Section 6 above is od's one clear win over both other tools: several -t flags in one command give you several rows per line of input, aligned under one offset. Hex over decimal over characters, each byte's readings stacked in a column. hexdump needs three separate -e strings to line the hex up against the text, and xxd cannot do it at all.
It is genuinely the right tool for "what is at offset 40, as a byte, as a number, and as a character" — which is most of what reading a binary format consists of.
What the two machines disagree about¶
Everything in the run above went through a helper that re-spaces the fields, because od output cannot be recorded raw: it is the one tool in this chapter with two independent implementations that lay out the same numbers differently. Here is what the helper is absorbing, and two things it cannot:
# macOS 26.6, BSD od
$ od -An -tx1 s.txt | cat -vet
63 61 66 c3 a9 3a 20 31 e2 82 ac 0a $
$
$ od -Ax -tx1 s.txt | cat -vet
0000000 63 61 66 c3 a9 3a 20 31 e2 82 ac 0a $
000000c$
$ od -Ax -tx1z s.txt | cat -vet
od: z: unrecognised format character$
$ od -An -tf4 s.txt | cat -vet
-2.303804e+02 2.331641e-09 1.661223e-32 $
$
# ubuntu:24.04, od (GNU coreutils) 9.4
$ od -An -tx1 s.txt | cat -vet
63 61 66 c3 a9 3a 20 31 e2 82 ac 0a$
$ od -Ax -tx1 s.txt | cat -vet
000000 63 61 66 c3 a9 3a 20 31 e2 82 ac 0a$
00000c$
$ od -Ax -tx1z s.txt | cat -vet
000000 63 61 66 c3 a9 3a 20 31 e2 82 ac 0a >caf..: 1....<$
00000c$
$ od -An -tf4 s.txt | cat -vet
-230.38042 2.3316409e-09 1.661223e-32$
Four disagreements, in rising order of how much they cost you.
1. Padding. BSD indents eleven spaces, pads a short line out to the width a full sixteen-byte line would need, and adds a trailing blank line. GNU indents one space and does none of it. Same numbers, and every column in a different place — which is why the shell examples here pipe od through a tidy helper, and why squeezing the spaces to read the output more comfortably is exactly how you delete the evidence.
2. The offset width under -A x. BSD prints seven hex digits, GNU prints six. Under -A d and -A o they agree at seven. So the one radix you are most likely to want for an offset is the one where the column changes width per machine.
3. z does not exist on BSD. GNU accepts a z suffix on a type — od -A x -t x1z — and adds a >caf..: 1....< text column, which makes it very nearly hexdump -C out of a tool that is always installed. It is a good flag and it is worth knowing that it is GNU-only: BSD od stops with "z: unrecognised format character". If a script of yours uses it, that script does not run on a Mac.
4. -t f4 formats the same float differently. BSD prints -2.303804e+02 where GNU prints -230.38042, and their exponent forms carry different precision as well. The value is the same; nothing that compares the two outputs as text will believe it.
The two rows to avoid¶
-t a and -t c are the friendliest-looking types and the two to be most careful with. -a prints a name for each byte and invents one for bytes above 127 — differently on each platform, and on BSD differently per locale, so the same file can come out spelling a plausible wrong word. -c is safer but still decodes multi-byte characters on macOS under a UTF-8 locale.
That whole story — the masking trick, the isprint() call, the question marks that were never od's output — is Inspecting a file, and it is worth reading once. The short version is the rule this library records: use -t c only with LC_ALL=C, and do not use -t a at all.
Against the other two¶
The hexdump page has the full four-tool table. What is on this side of it:
- It is always installed. The other three are not, and this is the whole reason to know it.
- Stacked
-ttypes, above — one pass, several readings, aligned. - Types rather than layout, so it reads values: signed, unsigned, floats, native
intwidth. Neitherxxdnorhexdump -Cwill tell you what bytes 4–8 are as a signed integer. -jand-Nopen a window without dumping the whole file, and keep the offsets counting from the front so what you quote is the address in the file.- It cannot go back. There is no
od -r;xxd -ris the only reverse here. - Its output is not portable. Not the numbers — the numbers are fine — but every column position, and under
-t fand-t athe text as well.
What to memorise¶
Two flags, and you have the tool:
od -An -tx1 file # the bytes, and nothing else
od -Ad -tx1 file # the same, with decimal offsets
od -An -tx1 -tc file # bytes and characters, aligned (LC_ALL=C)
od -Ad -j 1024 -N 64 -tx1 file # a 64-byte window at offset 1024
od -Ax -tx1z file # GNU only: near enough hexdump -C
-A picks the offset radix (d o x n), -t picks the type, -v turns off the * that collapses repeated lines, -j skips and -N stops.
If you are coming from Python or ABAP¶
Python. -t is struct.unpack with a shorter spelling, and the correspondence is exact enough to be useful: -t d2 is '<h', -t u1 is 'B', -t f8 is '<d', -t dI is 'i' with the same native-width caveat. Both make you name the size and the signedness, and both read the machine's byte order unless you say otherwise — struct at least makes you write the < or > and be reminded that a choice was made. When you find yourself running od -t several ways over the same offset, you have outgrown the shell: open(p,'rb').read() and struct.unpack_from will let you name the fields.
ABAP (Not machine-checked — CI cannot run ABAP.) The nearest thing is reading a file into an xstring and taking fields out of it with offsets, which is the same discipline: the byte position and the width are yours to state, and nothing in the type system checks them for you. The transferable habit is -t x1 itself — when a fixed-width interface misbehaves, look at the file one byte at a time before looking at the code, because the layout was probably designed in characters and written in bytes.
Try it¶
Then:
od yourfile | head -2, thenod -An -tx1 yourfile | head -2. The first is octal words at octal offsets; the second is the file.od -An -tx1 -tu1 -tc yourfile | head -6— three readings of every byte, stacked.od -Ax -tx1z yourfile | head -2. If it works you are on GNU; if it says unrecognised format character you are on a Mac.- Take any binary you have and read its first four bytes four ways:
-t x1,-t x4,-t dI,-t f4. Only one of them is a fact about the file.
Practice¶
Read od's interface as a C type. Predict what bare od prints for a 6-byte file — two things about it are surprising and neither is the byte values. Then write the flags that ask for hexadecimal, one byte at a time, decimal offsets.
Then say what od -t x1 -t d1 -t c does in one pass that no other dump on your machine can, and why this key refuses to print od -a output at all.
Answers
Verified output of od_kata_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
od WITH NO FLAGS
0000000 060543 141546 005251
0000006
Two things happened before a byte was printed. The offsets are OCTAL,
and the values are 16-bit WORDS in octal -- so neither column is in a
base you were thinking in, and the bytes are paired into integers.
THE FLAG THAT ASKS FOR WHAT YOU WANTED
0000000 63 61 66 c3 a9 0a
0000006
-A d address in decimal; -t x1 type: heXadecimal, 1 byte at a time.
That is the whole interface. -t takes a C type letter and a size, so
x1 x2 x4 are the same reading at three widths and d1 d2 d4 are signed
decimals of the same bytes.
ONE PASS, THREE READINGS -- THE THING NO OTHER DUMP DOES
0000000 63 61 66 c3 a9 0a
99 97 102 -61 -87 10
c a f 303 251 \n
0000006
Each -t adds a ROW under the same offsets, so you can line up the hex,
the signed decimal and the C escape for one byte without running the
file through three tools and hoping they agree about where they are.
WHAT THIS KEY WILL NOT PRINT, AND WHY
od -a names each byte -- nl, sp, ht for the low ones. For bytes above
0x7f the two implementations disagree: measured 2026-09-07, BSD od
prints c3 and a9 as themselves, while GNU od masks the high bit and
prints C and ) -- names for the ASCII characters 0x43 and 0x29, which
are not in the file at all.
So od -a output cannot be quoted without naming the machine it came
from, which makes it the wrong thing to paste into a bug report and
the reason this page exists.
WHEN TO REACH FOR od ANYWAY
It is the only dump POSIX guarantees, so it is the one that will be
there on a stripped container or an unfamiliar Unix. The two flags to
remember are the two that make it honest:
od -A d -t x1 file
and if you want the text column too, add -t c.
See also¶
hexdumpis a format engine wearing six presets — the four-tool comparison table, and the other lying defaultxxdis the dump you can put back — the one that reverses, and the honest default- Inspecting a file —
-t a's invented names, at length, and these tools inside a workflow - Reading a hex dump — the three columns, if this page assumed one you have not met
- Byte order and the BOM — what
-t x2and-t x4are asserting about your file