Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

dir — list directory contents, always in columns

Overview

dir is GNU ls wearing an MS-DOS costume: it is built from the same coreutils source as ls with a different set of compiled-in defaults, so that by default it lists entries in columns with backslash-escaped quoting, no matter whether stdout is a terminal or a pipe. It ships in the coreutils package (Debian bookworm) at /usr/bin/dir, next to ls and the third sibling vdir. The name exists for users arriving from MS-DOS, where DIR was the directory command; on Unix it found a second life as a way to get stable, pipe-independent columnar output.

dir is often confused with ls (same engine, terminal-adaptive defaults) and with vdir (the long-format twin). dir is equivalent to ls -C -b: same options, same sorting, same color support — only the defaults differ. It is not standardized by POSIX and is absent from macOS, the BSDs, and BusyBox, so scripts that call dir are silently GNU-only.

FieldValue
Packagecoreutils (Debian bookworm)
Man section1
Path/usr/bin/dir
First appearedGNU fileutils, early 1990s (modeled on the MS-DOS DIR command)
StandardsNone — GNU extension, not in POSIX

Synopsis

dir [OPTION]... [FILE]...

Common one-line forms:

dir                     # columns, escaped names, current directory
dir -l [file...]        # long format (same layout as ls -l)
dir -a /etc | head      # include dot entries, pipe-stable output
dir --color /usr/bin    # same LS_COLORS palette as ls

How It Works

One engine, three front doors

ls, dir, and vdir share one implementation; each binary just pins different default options. Everything documented for ls — sorting keys, -d, -a, -R, --time-style, --color, --quoting-style, exit codes — applies to dir verbatim. The full option matrix lives on the ls page; this page concentrates on what makes dir different.

The default-format decision

The only thing that separates everyday dir from everyday ls is how the output format is chosen:

                 ┌─────────────────────┐
                 │ stdout is a tty?    │
                 └──────┬───────┬──────┘
                 yes    │       │    no
                        ▼       ▼
   ls:      columns (-C),    one per line (-1),
            shell-escape     literal quoting,
            quoting, color   no color
            ────────────────────────────────
   dir:     columns (-C) and backslash escapes (-b) —
            unconditionally, on tty or pipe alike
            (color still only with --color)

Verified on this system: diff <(dir /usr/bin) <(ls -C -b /usr/bin) is empty — the two are byte-identical. Piped output proves the point:

$ printf 'abc\ndef\n' > a1; printf 'x' > b2
$ ls | head -2        # ls in a pipe: one per line
a1
b2
$ dir | head -2       # dir in a pipe: still columns
a1  b2

Escaped names by default

-b (--escape) prints C-style escapes for nongraphic characters, so a file containing a tab or newline shows as tab\tfile and nl\nfile even in scripts, while bare ls in a pipe prints the raw bytes:

$ touch $'tab\tfile' $'nl\nfile'
$ dir | cat -A | tail -2      # cat -A makes the $ end-of-line visible
tab\tfile$
nl\nfile$

This is the practical reason dir exists beyond MS-DOS nostalgia: filenames cannot silently inject control characters into your terminal or log files.

Sorting and selection

Default sort is alphabetical under the current locale’s collation (“Sort entries alphabetically if none of -cftuvSUX nor --sort is specified”, as --help puts it). -t, -S, -v, -U, -r and friends behave exactly as in ls. -a includes dot entries, -A drops only . and .., -d describes directories themselves, -R recurses.

Options That Matter

Format (dir’s reason to exist)

OptionEffect
-C (default)Fill columns vertically, like ls -C, forced even into pipes
-b (default)C-style escapes for nongraphic characters, forced even into pipes
-lLong format — makes dir behave like vdir
-1, -m, -xOne per line / comma-separated / across rows
-FAppend type indicators (/, *, @, |, =)
-hHuman-readable sizes with -l/-s

Selection, sorting, display

OptionEffect
-a, -AInclude dot entries / minus . and ..
-dList directories themselves, not contents
-RRecurse into subdirectories
-t, -S, -v, -U, -rmtime / size / version / directory-order / reverse sort
-i, -sInode numbers / allocated blocks
--color[=WHEN]Colorize via LS_COLORS (see dircolors)
--quoting-style=WORDOverride the forced -b escaping (literal, shell, c, …)
-I PATTERNIgnore matching entries

Usage Patterns

# MS-DOS muscle memory: plain directory listing in columns
dir
# Stable columnar output into a pipe or log — identical to on-screen
dir /usr/bin | head -20
# Long format without changing binary: same layout as ls -l
dir -lh /var/log | head
# Newest entries first, columnar, escaped — a quick "what changed here"
dir -t /etc | head
# Include hidden files
dir -a ~ | head
# Describe directories themselves, not their contents
dir -d /etc/* | head
# Recursion with classification markers
dir -FR /opt | head -30
# Same palette as ls: colors driven by LS_COLORS from dircolors
dir --color /usr/lib | head
# Control-character-proof listing for a suspicious directory
dir /tmp/upload | cat -A | head
# Natural version order for release artifacts
dir -v releases/ | head
# Reproducible ordering regardless of locale
LC_ALL=C dir /usr/bin | head

Nuances and Gotchas

  • Not portable. dir is a GNU extension: no macOS, no BSD, no BusyBox. A script using dir breaks the moment it meets a non-GNU system, while ls -C works everywhere. POSIX specifies ls, not dir/vdir.
  • It is still ls underneath. Every ls parsing trap applies: locale collation reorders names, column formats wrap at terminal width, and parsing dir output in scripts is as wrong as parsing ls. Machine-readable listing belongs to globs, find -print0 | xargs -0, or for f in *.
  • Escapes are display, not round-trip. dir prints nl\nfile but that string is not a shell-quoting of the name; you cannot eval it back. For safe re-reading use ls -Q (double-quote style) or, better, don’t parse listings at all.
  • Color does not turn on by itself. Unlike interactive ls setups that alias --color=auto, dir ships colorless; pass --color explicitly and configure the palette via dircolors.
  • Exit codes match ls. 1 for minor problems (unreadable subdirectory during -R), 2 for serious trouble (an inaccessible command-line operand). Scripts that only test "$?" -eq 0 miss partial failures.
  • -b vs --quoting-style=escape. The default forced quoting is C escapes; filenames with spaces stay bare (no quotes added), so word-splitting on output is still a hazard.

Exit Status

CodeMeaning
0Listed everything requested
1Minor problems (e.g. an unreadable subdirectory encountered during -R)
2Serious trouble (e.g. cannot access a command-line argument)
  • ls — the terminal-adaptive parent command; dir ≡ ls -C -b.
  • vdir — the long-format sibling; vdir ≡ ls -l -b.
  • dircolors — generates the LS_COLORS database --color consumes.
  • Collection overview — the GNU Coreutils chapter hub.
  • find — recursive, predicate-based listing when one level isn’t enough.
  • xargs — the correct way to consume filename lists (instead of parsing listings).
  • man pages — where the complete shared option matrix is documented.

Interview Questions

Q: Why do dir and vdir exist when ls can do everything they do?

They are the same code with different compiled-in defaults, created for MS-DOS converts (dir = columns) and for a guaranteed long format (vdir = ls -l -b). The modern justification is determinism: their output format does not change when stdout stops being a terminal, whereas ls silently switches from columns to one-per-line and from shell-escape to literal quoting. That said, the better scripting answer is to pass explicit flags to ls — or not parse listings at all.

Q: What exactly does dir print by default, and how do you prove it?

dir defaults to ls -C -b: entries sorted alphabetically by locale collation, filled into columns vertically, nongraphic characters shown as C escapes — on a terminal and in a pipe alike. The proof is a byte comparison: diff <(dir /usr/bin) <(ls -C -b /usr/bin) produces no output. Colors are not part of the deal; --color must be requested explicitly.

Q: Is dir POSIX? What breaks if a build script depends on it?

No. POSIX.1-2018 specifies ls but not dir or vdir; they are GNU coreutils extensions. macOS and the BSDs do not ship them, and BusyBox does not either, so a script calling dir fails on those platforms with “command not found”. Portable scripts use ls -C (or ls -l) with explicit flags.

Q: A colleague says “dir output is safe to parse because it escapes weird characters.” What’s wrong with that?

Escaping only neutralizes nongraphic bytes; it does not make the output structurally parseable. Names with spaces are not quoted, locale collation can reorder entries, columns wrap at terminal width, and the escape format (nl\nfile) is for human eyes — not a shell-safe quoting you can eval back to the original name. Filename-safe automation still requires globs, find -print0 | xargs -0, or NUL-delimited data.

Q: What are dir’s exit codes and when does it return 1 vs 2?

Same contract as ls: 0 when everything listed, 1 for minor problems such as an unreadable subdirectory encountered during a recursive listing (the rest of the output is still produced), and 2 for serious trouble such as an inaccessible command-line operand. Checking only for nonzero status conflates “partial listing” with “total failure”.

References