Skip to content

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:

Measured 2026-09-07 — macOS 26.6 (BSD od) and ubuntu:24.04 (od GNU coreutils 9.4), s.txt = 'café: 1€' in UTF-8, LC_ALL=C. Verbatim, through cat -vet so the padding is visible; not machine-checked, because no key matches both.
# 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 -t types, above — one pass, several readings, aligned.
  • Types rather than layout, so it reads values: signed, unsigned, floats, native int width. Neither xxd nor hexdump -C will tell you what bytes 4–8 are as a signed integer.
  • -j and -N open 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 -r is the only reverse here.
  • Its output is not portable. Not the numbers — the numbers are fine — but every column position, and under -t f and -t a the 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

cd 11_Tools/od/examples
bash od_reads_types_sh.sh

Then:

  1. od yourfile | head -2, then od -An -tx1 yourfile | head -2. The first is octal words at octal offsets; the second is the file.
  2. od -An -tx1 -tu1 -tc yourfile | head -6 — three readings of every byte, stacked.
  3. od -Ax -tx1z yourfile | head -2. If it works you are on GNU; if it says unrecognised format character you are on a Mac.
  4. 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