Verdict: install it, alias ls/ll/lt to it, and enjoy the colors, the git column, and the built-in tree — but search for the right name and keep plain ls in your scripts. eza is ls with sane colors, a Git status column, a real tree mode, and human sizes by default. For looking at a directory at the terminal it’s a genuine upgrade. The catches aren’t price or telemetry — they’re a dead twin with a confusing name, one column that silently disappears, and a long-format layout that isn’t ls -l byte-for-byte. We use it daily. We also tripped over all three while writing this, and they’re in the box.
eza is free and open source (MIT). We have no relationship with the project and nothing to sell. Like its siblings ripgrep, fd, and bat, the dealbreakers here are a few defaults that surprise anyone arriving from coreutils. We’ll show you exactly where, with output we actually captured on a fresh Ubuntu 24.04 box.
Install — and the first surprise is which name is dead
brew install eza # macOS
sudo apt install eza # Debian/Ubuntu (24.04+)
If you went looking for “the modern ls in Rust” a couple of years ago, you found exa — and that’s the trap. exa is the original, and it is over: the repo was archived and the last release was 2023. eza is the community fork that picked it up and is the one being maintained. Every blog post that still says exa is pointing you at a tombstone.
Ubuntu makes this extra confusing, because it tries to be helpful. Watch what the eza package actually installs:
$ dpkg -L eza | grep -E '/bin/'
/usr/bin/eza
/usr/bin/exa
$ ls -l /usr/bin/exa
lrwxrwxrwx 1 root root 3 Feb 13 2024 /usr/bin/exa -> eza
$ exa --version
eza - A modern, maintained replacement for ls
v0.18.2 [+git]
So on Ubuntu, typing exa works — but it’s lying to you. There’s a compatibility symlink, and the dead name silently runs the live tool. That’s friendly until you copy an apt install exa line onto a box that doesn’t have the symlink:
$ apt-cache policy exa
exa:
Installed: (none)
Candidate: (none)
No candidate. The package you’d search for doesn’t exist; the binary you’d type is a redirect to a different project. The honest rule: search for eza, install eza, and treat any exa that works as a courtesy, not a guarantee.
Why you’d switch from ls
Here’s the whole pitch in one screen — eza -lah --git, the way we actually type it every day:
$ eza -lah --git
Permissions Size User Date Modified Git Name
.rw-r--r-- 7 runner 29 Jun 10:55 -N .env
drwxr-xr-x - runner 29 Jun 10:55 -I .git
drwxr-xr-x - runner 29 Jun 10:55 -- .hidden
.rw-r--r-- 4 runner 29 Jun 10:55 -N app.log
drwxr-xr-x - runner 29 Jun 10:55 -- docs
lrwxrwxrwx - runner 29 Jun 10:55 -N latest.py -> src/main.py
.rw-r--r-- 23 runner 29 Jun 10:55 -M README.md
drwxr-xr-x - runner 29 Jun 10:55 -N src
.rw-r--r-- 10 runner 29 Jun 10:55 -N untracked.txt
(The colors don’t survive copy-paste into a Markdown block, but in a real terminal the directories, the symlink, and the permission bits are all colored.) Three things ls can’t do for free are right there: sizes are already human (23, not 23-but-you-asked-for--h), the symlink target is shown, and that Git column tells you -M for a modified tracked file, -N for something new/untracked, -I for ignored, -- for unchanged. A tiny git status you didn’t have to ask for.
And the tree mode means you no longer need a separate tree package:
$ eza --tree --level=2
.
├── app.log
├── docs
├── latest.py -> src/main.py
├── README.md
├── src
│ └── main.py
└── untracked.txt
It also speaks a lot of ls’s dialect, which makes the switch painless: -l, -a, -1, -h, --color=never, even --time-style=full-iso all work as you’d expect. It is friendlier than the “it’s a whole new tool” fear suggests.
Surprise 1: the Git column silently vanishes outside a repo
The --git column is the best reason to switch — and the easiest to lose without noticing. Ask for it inside a git repo and you get it. Ask for it outside one and eza doesn’t warn you; the column isn’t there at all:
$ eza -l --git --header /etc/hostname
Permissions Size User Date Modified Name
.rw-r--r-- 14 root 22 Jun 22:36 /etc/hostname
No Git header, no error, no “not a repository” note — the flag you typed quietly did nothing. This is fine once you know it, but the first time you wonder why your shiny git column disappeared, the answer is “you cd‘d out of the repo,” not “the flag broke.”
Surprise 2: eza -l is not ls -l, so stop parsing column 5
Here’s the one that bites scripts. The classic muscle-memory move is “the file size is field 5 of ls -l.” It is — for GNU ls. It is not for eza, because the columns are in a different order:
$ ls -l README.md | awk '{print "field5="$5}'
field5=23
$ eza -l README.md | awk '{print "field5="$5}'
field5=Jun
GNU ls -l field 5 is the size (23). eza -l field 5 is the month (Jun), because its layout is Permissions, Size, User, Date… — size is field 2, and the date eats three fields. Anything that parses ls -l output by column position gets garbage from eza.
The saving grace: this only bites if you actually replace ls in a parsing context. An alias ls=eza lives in interactive shells only, so a real script that calls ls still gets coreutils. But the moment you paste a ls -l | awk '{print $5}' one-liner into a session where ls is aliased — or write a shell function that does — you’re parsing the wrong column. Parse ls (or better, stat -c %s) for sizes; let eza be the thing your eyes read.
Surprise 3: the default sort isn’t the one you’ve memorized
A subtle one. eza sorts case-insensitively; GNU ls (in the C locale) sorts ASCII, capitals first. Same directory, different order:
$ ls # capitals first
README.md app.log docs latest.py src
$ eza # case-insensitive
app.log docs latest.py README.md src
Neither is wrong, but if you rely on “uppercase files float to the top,” eza will quietly reshuffle them into the alphabet. Worth knowing before you go hunting for a README that “moved.”
A note on icons (and tofu)
Half the screenshots that sell eza show little file-type icons. Those come from --icons, and they need a Nerd Font installed and selected in your terminal. Without one you get the glyphs as literal mojibake — boxes and garbage bytes where the icon should be. The colors and the git column work in any terminal; the icons are an opt-in that costs you a font install. Skip them until you’ve set the font up, or you’ll think the tool is broken.
Where plain ls still wins
eza is for humans looking at directories. ls is for plumbing and portability. Plain ls wins whenever:
- You’re scripting or parsing. Stable, documented columns; on every machine under one name; no surprise reordering. (And
lsis POSIX —ezais an extra dependency your script can’t assume.) - You’re on a box you don’t control.
lsis always there.ezais a thing you have to install, and the name you’d reach for (exa) might be the dead one. - You want byte-identical, locale-stable output.
ls --color=never -lin the C locale is a known quantity that downstream tools have parsed for decades.
What it costs and the free alternative
It costs nothing — open source, no account, no telemetry, no paid tier. The free alternative is the one already on your machine: ls. The honest trade is reading comfort (colors, git column, tree, human sizes, sane defaults) versus plumbing stability (one name everywhere, fixed columns, POSIX). They’re a division of labor, not a duel: alias eza to the directory-glancing half of your brain and leave ls for the scripts.
A starter set for ~/.bashrc (interactive only, on purpose):
alias ls='eza --group-directories-first'
alias ll='eza -lah --git --group-directories-first'
alias lt='eza --tree --level=2'
What made us close the tab
Nothing — eza is staying on every machine, aliased to the directory-reading half of our brain. The honest caveats, in the order they’ll bite you:
- The name you’d search for is dead.
exais archived;ezais the fork. On Ubuntuexais a symlink toeza; elsewhereapt install exafinds nothing. Search and installeza. --gitis silent when it does nothing. Outside a repo the column isn’t there at all — no warning. If your git status vanished, you left the repo.eza -l≠ls -l. Different column order; field 5 is the month, not the size. Don’t parse it — and remember an alias won’t reach your scripts, which is exactly why your scripts should keep callingls.
When it goes wrong: if a tree of icons turns into boxes and garbage, it’s a missing Nerd Font, not a broken install — drop --icons and the colors still work. If a “modern ls” tutorial command does nothing, check the name: you almost certainly typed exa on a box that only knows eza. And if a one-liner that worked yesterday suddenly prints the wrong field, check whether ls is aliased to eza in that shell — then parse ls or stat instead.