Swap ls, cat, find and grep for eza, bat, fd and ripgrep

Install eza, bat, fd and ripgrep, learn the side-by-side commands and flag differences, and alias them without breaking your scripts.

how-to7 steps

Updated

in short

Install eza, bat, fd and ripgrep, alias ls and cat in your interactive rc file, and call fd and rg by their own names. Leave scripts on ls, cat, find and grep, because several flags mean different things.

These four tools cover the same jobs as the classic commands with different defaults: colour and Git status in listings, syntax highlighting when reading files, and searches that skip hidden and .gitignored files. Those defaults are what make them faster to type, and also why they are not drop-in replacements for scripts.

This guide installs all four, shows the equivalent commands side by side, points out the flags that differ, and sets up aliases that only apply in your interactive shell.

Tools used

Before you start

  • A package manager such as Homebrew, apt, dnf or pacman
  • ~/.local/bin on your PATH if you are on Debian or Ubuntu (for the symlinks in step 2)

Steps

  1. Step 1: Install the four tools

    All four are packaged by Homebrew and most Linux distributions. On Debian and Ubuntu the fd package is called fd-find, and eza is in the archive from Debian 13 and Ubuntu 24.04; on older releases use the apt repository described in eza's INSTALL.md. The ripgrep binary is rg.

    sh
    brew install eza bat fd ripgrep
    sh
    sudo apt install eza bat fd-find ripgrep
    sh
    sudo pacman -S eza bat fd ripgrep
  2. Step 2: Fix the binary names on Debian and Ubuntu

    Debian and Ubuntu install fd as fdfind, because fd is taken by another package, and some releases install bat as batcat for the same reason. Symlink them into ~/.local/bin so the commands in this guide work. Skip this on other systems.

    sh
    mkdir -p ~/.local/bin
    ln -s "$(which fdfind)" ~/.local/bin/fd
    ln -s /usr/bin/batcat ~/.local/bin/bat   # only if `bat` is not found
    sh
    eza --version && bat --version && fd --version && rg --version
  3. Step 3: List files with eza

    Most short listing flags carry over: -l, -a, -1, -R, -r. Sizes in -l output are already human-readable. eza adds --git for per-file Git status and --tree with --level for depth.

    Watch three flags. -t picks which timestamp to show and needs a value, so ls -lt becomes eza -l -s modified -r (eza sorts oldest first, -r flips it). -h adds a header row instead of human-readable sizes. And -s sorts by a named field rather than showing block sizes.

    sh
    # ls -la
    eza -la
    
    # ls -lt (newest first)
    eza -l -s modified -r
    
    # tree -L 2, skipping gitignored files
    eza --tree --level=2 --git-ignore
    
    # long listing with a Git status column
    eza -l --git
  4. Step 4: Read files with bat

    bat file prints with syntax highlighting, line numbers and Git change markers, and pipes long output through a pager. When its output goes to a pipe or a file, bat prints the plain contents like cat, so bat a b > c still concatenates.

    -p drops the decorations, -pp also turns off paging, -A shows non-printable characters like cat -A, and -l sets the language for stdin or unknown extensions.

    sh
    bat src/main.rs
    bat -pp config.toml        # plain, no pager
    bat -A script.sh           # show tabs, newlines, other control characters
    curl -s https://example.com | bat -l html
  5. Step 5: Find files with fd

    fd PATTERN matches a regular expression anywhere in the file name, so fd report behaves like find . -iname '*report*'. Search is case-insensitive unless the pattern contains an uppercase letter. Use -e for an extension, -g for an exact glob, -t f or -t d for files or directories, and -p to match against the full path.

    The main difference from find: fd skips hidden files and anything matched by .gitignore when it is inside a Git repository. Add -H for hidden files, -I for ignored files, or -u for both.

    sh
    # find . -name '*.md'
    fd -e md
    
    # find . -type f -name 'Dockerfile'
    fd -t f -g Dockerfile
    
    # include hidden and ignored files, like find does
    fd -u '\.env$'
    sh
    # find . -name '*.zip' -exec unzip {} \;
    fd -e zip -x unzip
    
    # one command with all results, like find ... -exec cmd {} +
    fd -e md -X wc -l
  6. Step 6: Search file contents with ripgrep

    rg PATTERN searches the current directory recursively, so there is no -r. It skips gitignored, hidden and binary files; -u drops the ignore files, -uu adds hidden files and -uuu searches everything, like grep -r. Line numbers show by default in a terminal.

    Patterns use Rust regex syntax, which is close to grep -E: alternation is a|b, not the a\|b of default grep. Backreferences and look-around need -P (PCRE2), which depends on how your copy was built. Familiar flags still work: -i, -w, -l, -v, -F, -c. -S turns on smart case, and -t and -g filter by file type or glob.

    sh
    # grep -rn 'TODO' .
    rg TODO
    
    # grep -rl --include='*.py' 'import requests' .
    rg -l -t py 'import requests'
    
    # literal string with regex characters
    rg -F 'user.id == 0'
    
    # exclude a directory by glob
    rg -g '!vendor/' 'func main'
  7. Step 7: Alias them in your interactive shell only

    Put aliases in ~/.bashrc, ~/.zshrc or fish's config.fish, which interactive shells read and scripts do not. bash does not expand aliases in non-interactive scripts unless they run shopt -s expand_aliases, so scripts keep calling the real ls and cat.

    Alias ls and cat only. find and grep take different arguments from fd and rg, so aliasing them breaks muscle memory in both directions; type fd and rg instead. To reach the original command past an alias, run command ls or \ls.

    sh
    # ~/.bashrc or ~/.zshrc
    alias ls='eza'
    alias ll='eza -l --git'
    alias la='eza -la'
    alias lt='eza --tree --level=2'
    alias cat='bat --paging=never'
    fish
    # ~/.config/fish/config.fish
    alias ls 'eza'
    alias ll 'eza -l --git'
    alias cat 'bat --paging=never'

Keep scripts on the originals

  • ls, cat, find and grep are on every POSIX system; eza, bat, fd and rg are not, so shipped scripts and CI images should not depend on them.
  • fd and rg silently skip hidden and gitignored files. A script that must see every file needs find/grep, or fd -u and rg -uuu.
  • eza with no path argument reads file names from stdin when stdin is not a terminal. In cron jobs or pipelines, pass . explicitly.
  • fd -x runs commands in parallel and in no fixed order. Use --threads=1 or -X if order matters.
  • rg exits 0 on a match, 1 on no match and 2 on an error, the same as grep, so if rg -q ... works in scripts.

Troubleshooting

  • fd: command not found on Debian or Ubuntu: the binary is fdfind. Create the symlink from step 2.
  • bat opens a pager when you only wanted output: pass --paging=never, or set BAT_PAGING=never to make it permanent.
  • eza -lt fails with an error about --time <FIELD>: in eza -t selects which timestamp to show. Sort with -s modified -r instead.
  • rg or fd misses a file you can see: it is hidden or matched by .gitignore, .ignore or .rgignore/.fdignore. Add -u.
  • rg prints backreferences are not supported: add -P to use PCRE2, if your build includes it (rg --version lists +pcre2).

Also worth a look

  • lsdAnother ls replacement with colours, icons and a tree view.
  • exaThe project eza was forked from; exa is unmaintained and points users to eza.
  • bfsBreadth-first find that keeps find's syntax, for when you want find's flags rather than fd's.
  • rgaWraps ripgrep to search inside PDFs, Office documents, archives and other formats.

FAQ

Can I alias grep to rg?
You can, but rg's defaults and some flags differ from grep's, so commands copied from documentation may behave differently. Typing `rg` keeps both available.
Is bat safe to use as cat in pipelines?
Yes. When output is not a terminal, bat prints the plain file contents with no line numbers, colours or pager.
Why does fd not find my .env file?
fd skips hidden files by default. Use `fd -H '\.env'`, and add `-I` if the file is also in `.gitignore`.

related