Skip to main content
ansicode

LS_COLORS — ANSI colours for ls, dircolors, file listings

GNU `ls` reads the `LS_COLORS` environment variable on every invocation — a colon-separated string of `type=SGR` and `*.ext=SGR` pairs that colours each entry by file type and suffix. BSD-derived `ls` (macOS, FreeBSD, OpenBSD) reads a totally different env var `LSCOLORS` with a fixed-position 22-character format and no extension support. Seven copy-paste snippets below cover the full LS_COLORS map: env-var syntax, `dircolors` workflow, the BSD divergence, the GNU canonical default, `eza` / `exa` extensions, `--color` / `NO_COLOR` / `CLICOLOR` overrides, and per-shell wire-up for bash / zsh / fish / csh.

Configuration snippets

LS_COLORS — the colon-separated key=value string syntax

GNU `ls --color=auto` reads the `LS_COLORS` environment variable on every invocation. It is a single colon-separated string of `key=SGR` pairs. Keys come in two flavours: **type tokens** (two-letter mnemonics matching file types — `di` directory, `ln` symlink, `ex` executable, `fi` regular file, `pi` pipe / FIFO, `so` socket, `bd` block device, `cd` character device, `or` orphaned symlink, `mi` missing target, `tw` writable sticky directory, `ow` writable other directory, `st` sticky directory, `su` setuid, `sg` setgid, `ca` capability, `mh` multi-hardlink, `do` Solaris door) and **extension tokens** (`*.ext=SGR` patterns that match by trailing filename suffix, case-sensitive by default — match-by-glob is NOT supported, only exact suffix). Values are SGR-parameter strings without the leading `\x1b[` or trailing `m`: `01;31` = bold red foreground, `38;5;208` = 256-palette orange, `38;2;255;128;0` = 24-bit truecolor. Tokens are checked left-to-right at first hit; later entries override earlier ones.

shell / env
# Minimal LS_COLORS — a few type tokens + a few extension tokens:
export LS_COLORS='di=01;34:ln=01;36:ex=01;32:*.tar=01;31:*.zip=01;31:*.gz=01;31:*.md=00;33'

# What each pair means:
#   di=01;34         directories          → bold blue
#   ln=01;36         symlinks             → bold cyan
#   ex=01;32         executables          → bold green
#   *.tar=01;31      .tar files           → bold red
#   *.zip=01;31      .zip files           → bold red
#   *.gz=01;31       .gz files            → bold red
#   *.md=00;33       .md files            → yellow

# 256-palette + truecolor (coreutils 8.32+, 2020):
export LS_COLORS='di=38;5;33:*.json=38;2;255;128;0'
#   di=38;5;33       directories          → palette index 33 (blue)
#   *.json=38;2;…    .json files          → orange 24-bit

# Important: case-sensitive by default. Match .JPG separately from .jpg
# unless you set TZ-style insensitivity via dircolors --print-database flags.
export LS_COLORS='*.jpg=01;35:*.JPG=01;35:*.png=01;35:*.PNG=01;35'

# Verify the env var is set, paged for readability:
echo "$LS_COLORS" | tr ':' '\n'

dircolors — turn a human-readable database into LS_COLORS

Hand-writing one giant colon-separated string is painful. `dircolors` consumes a multi-line, comment-friendly database (one key per line, `# comments`, section headers) and emits the `LS_COLORS=…; export LS_COLORS` shell command. Most distros ship `/etc/DIR_COLORS` (read-only) plus a user-editable `~/.dir_colors`. `dircolors -p` prints the built-in default to stdout — pipe that into a file, edit it, then `eval $(dircolors -b ~/.dir_colors)` from your `.bashrc`. The output format is shell-aware: `-b` emits bash / sh syntax, `-c` emits csh / tcsh syntax. Fish users need a different wire-up — see snippet 7.

shell / env
# 1. Generate a template — built-in default, fully commented:
dircolors -p > ~/.dir_colors

# Sample contents look like:
#
#   # Files of special type
#   DIR    01;34       # directory
#   LINK   01;36       # symbolic link (a 'target' here for ls -l)
#   EXEC   01;32       # files with execute permission
#
#   # Archives or compressed (bright red)
#   .tar   01;31
#   .tgz   01;31
#   .zip   01;31
#
#   # Image formats (bright magenta)
#   .jpg   01;35
#   .png   01;35

# 2. Edit ~/.dir_colors to taste, then wire it into ~/.bashrc:
echo 'eval "$(dircolors -b ~/.dir_colors)"' >> ~/.bashrc

# 3. Reload the shell or:
eval "$(dircolors -b ~/.dir_colors)"

# Verify it landed:
ls --color=always | head

# csh / tcsh form:
eval \`dircolors -c ~/.dir_colors\`

BSD ls — LSCOLORS is a TOTALLY different format (page's #1 differentiator)

macOS, FreeBSD, OpenBSD, NetBSD ship the BSD-derived `ls` — which does NOT read `LS_COLORS`. It reads `LSCOLORS` (note: no underscore) and the format is a fixed-position 22-character string where each pair of characters encodes (foreground, background) for a specific file type slot in a hard-coded order: `directory · symlink · socket · pipe · executable · block-device · char-device · setuid · setgid · sticky-other-writable · other-writable`. Each character is one of `a-h` (lowercase = colour) / `A-H` (uppercase = bold colour) / `x` (default). Letters map: `a=black b=red c=green d=brown / yellow e=blue f=magenta g=cyan h=light-gray`. There is NO extension-by-file-type support — BSD `ls` colours by type only, never by `.tar` / `.zip` suffix. Toggle colour with `ls -G` (BSD default off) or `CLICOLOR=1` in env; force on pipe with `CLICOLOR_FORCE=1`. Most cross-platform dotfiles set BOTH `LS_COLORS` and `LSCOLORS` so the same `~/.bashrc` works on Linux and macOS.

shell / env
# BSD LSCOLORS — 11 pairs (22 chars), hard-coded slot order:
#
#   pos  slot                           default
#   1-2  directory                      ex      (blue, default-bg)
#   3-4  symlink                        fx      (magenta, default-bg)
#   5-6  socket                         cx      (green, default-bg)
#   7-8  pipe / FIFO                    dx      (brown / yellow, default-bg)
#   9-10 executable                     bx      (red, default-bg)
#   11-12 block device                  egcx    -- wait, this is wrong;
#                                                  the canonical slot is:
#         block device                  cd      (green, brown)
#   13-14 char device                   ed      (blue, brown)
#   15-16 setuid                        ab      (black, red)
#   17-18 setgid                        ag      (black, cyan)
#   19-20 sticky+other-writable         hb      (light-gray, red)
#   21-22 other-writable                hc      (light-gray, green)
#
# Letter map (BSD-specific, NOT ANSI / SGR numerics):
#   a=black b=red c=green d=brown e=blue f=magenta g=cyan h=light-gray
#   uppercase = bold version of the same colour
#   x         = default terminal colour
#
# BSD ls colour default — the value of LSCOLORS if you don't set it:
export LSCOLORS='exfxcxdxbxegedabagacad'

# Enable colour on BSD ls — default-off, opposite of GNU ls:
export CLICOLOR=1            # honour TTY detection
export CLICOLOR_FORCE=1      # force on pipe (skip if you pipe to grep)

# Verify with ls -G (BSD-flag) or just ls (when CLICOLOR is set):
ls -G

# Cross-platform dotfile — set BOTH so Linux + macOS work from the same file:
export LS_COLORS='di=01;34:ln=01;36:ex=01;32:*.tar=01;31:*.zip=01;31'
export LSCOLORS='ExFxCxDxBxegedabagacad'      # uppercase = bold on BSD

# Or just install GNU coreutils on macOS for unified behaviour:
#   brew install coreutils
#   alias ls='gls --color=auto'
# Then LS_COLORS applies on macOS too.

The canonical GNU LS_COLORS default — copy-pasteable

If you just want the default that `dircolors -p | dircolors --bourne-shell` would produce on a stock GNU coreutils 9.x installation, here it is in one line. Save it to `.bashrc` and you have the upstream defaults without running `dircolors` at all — useful for minimal containers / Alpine / scratch images / distroless contexts where `dircolors` may not be installed. Type tokens come first, then a long tail of extension tokens grouped by colour (archives in red, images in magenta, audio in cyan, source code in default). Last writer wins, so put narrow extension overrides AFTER broad type tokens.

shell / env
# Canonical GNU coreutils 9.x default LS_COLORS, one line:
export LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=00:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:'

# Notes on the special type tokens at the front:
#   rs=0            reset (delimits coloured regions in a row)
#   mh=00           multi-hardlink — empty value = use default
#   pi=40;33        pipe / FIFO  → black bg, yellow fg
#   so=01;35        socket       → bold magenta
#   do=01;35        Solaris door → bold magenta
#   bd=40;33;01     block dev    → black bg, bold yellow fg
#   cd=40;33;01     char dev     → black bg, bold yellow fg
#   or=40;31;01     orphan link  → black bg, bold red fg
#   mi=00           missing target — empty = use default
#   su=37;41        setuid       → white fg, red bg
#   sg=30;43        setgid       → black fg, yellow bg
#   ca=00           capability — empty = use default (capabilities deprecated)
#   tw=30;42        sticky + other-writable → black fg, green bg
#   ow=34;42        other-writable          → blue fg, green bg
#   st=37;44        sticky                  → white fg, blue bg
#   ex=01;32        executable              → bold green

eza / exa — modern ls replacements with their own colour systems

`exa` (deprecated, last commit 2022) and its actively-maintained fork **`eza`** are popular `ls` replacements. They honour `LS_COLORS` for backward compatibility BUT also support their own richer per-element colour env var `EXA_COLORS` / `EZA_COLORS` with extra slots GNU `ls` lacks: file permissions (`ur` user-read, `uw` user-write, `ux` user-execute, plus `gr` `gw` `gx` `tr` `tw` `tx` group / other), inode columns (`in`), size unit suffixes (`sn` size-number, `sb` size-unit-byte), date columns (`da` accent date, `df` future-date, etc.), and git status columns (`ga` added, `gm` modified, `gd` deleted, `gn` renamed, `gv` updated). Same `key=SGR` syntax. Set `EZA_COLORS=reset` first to drop the defaults if you want a pure custom palette.

shell / env
# Minimal eza override — keep LS_COLORS, add eza-specific slots:
export EZA_COLORS='ur=33:uw=31:ux=32:gr=33:gw=31:gx=32:in=38;5;240:sn=38;5;33:sb=38;5;240'

# What each pair means in eza output:
#   ur=33        user read         → yellow
#   uw=31        user write        → red
#   ux=32        user execute      → green
#   gr=33        group read        → yellow
#   gw=31        group write       → red
#   gx=32        group execute     → green
#   in=38;5;240  inode column      → 256-palette dim grey
#   sn=38;5;33   size number       → palette blue
#   sb=38;5;240  size byte unit    → palette dim grey

# eza git-status column slots (only when --git is passed):
export EZA_COLORS='ga=32:gm=33:gd=31:gn=35:gv=36:ga|gm|gd|gn|gv=01'
#   ga=32        added             → green
#   gm=33        modified          → yellow
#   gd=31        deleted           → red
#   gn=35        renamed           → magenta
#   gv=36        updated           → cyan

# Reset eza defaults and start from a clean slate:
export EZA_COLORS='reset:ur=33:uw=31:ux=32'

# Same syntax for the older exa binary:
export EXA_COLORS='reset:ur=33:uw=31:ux=32'

# Aliases for ls drop-in:
alias ls='eza --color=auto'
alias ll='eza -l --git --color=auto'
alias la='eza -la --color=auto'
alias tree='eza --tree --color=auto'

--color flag · NO_COLOR · CLICOLOR · CLICOLOR_FORCE — per-invocation overrides

GNU `ls` accepts `--color={auto,always,never}`. `auto` (the default since most distros' `LS_COLORS` setup) emits SGR only on TTY; `always` forces colour even into pipes (useful with `less -R`); `never` disables. The universal `NO_COLOR` env var (since coreutils 8.30 / 2018) overrides everything — sets `--color=never` effectively. BSD `ls` uses different flags: `-G` enables colour (default off), `CLICOLOR=1` env equivalent, `CLICOLOR_FORCE=1` forces colour through pipes. `eza` / `exa` mirror GNU's `--color` flag and honour `NO_COLOR`. Tip: use `--color=always` only when piping into a renderer (`less -R`, `bat`, `aha`) — piping into `grep` / `wc` with colour active inserts SGR into the match string and breaks naive substring matching.

shell / env
# GNU ls — three colour modes:
ls --color=auto         # TTY-only (the sensible default)
ls --color=always       # force colour through pipes
ls --color=never        # disable colour entirely
NO_COLOR=1 ls           # universal env opt-out (coreutils 8.30+)

# Pipe coloured listing into a pager that renders SGR:
ls --color=always -la | less -R

# BSD ls — different flag set:
ls -G                   # one-shot enable (BSD-only flag)
CLICOLOR=1 ls           # env equivalent
CLICOLOR_FORCE=1 ls | grep .conf   # force through pipe (BSD)

# eza / exa equivalents:
eza --color=always -la | bat
NO_COLOR=1 eza          # honoured

# Defeat parent shell's alias for one invocation:
\\ls                    # bypasses alias, runs the binary directly
command ls --color=never

# Common trap — colour bytes break grep substring matching:
ls --color=always | grep ".conf"       # wrong — may miss matches
ls --color=never  | grep ".conf"       # right — clean bytes
ls --color=always | grep --color=never ".conf"   # right — strip in grep

# Verify NO_COLOR is honoured site-wide (coreutils, grep, ripgrep, etc.):
env | grep -i color
NO_COLOR=1 ls && NO_COLOR=1 grep '' file.txt && NO_COLOR=1 rg pattern

Per-shell wire-up — bash · zsh · fish · csh

`LS_COLORS` is a plain env var; any shell that exports env vars can set it. The wire-up differs per shell. `bash` and `zsh` share the `eval $(dircolors -b)` pattern. `fish` is friendlier — `set -gx LS_COLORS …` works directly, and fish ships its own per-element colours configurable via `set fish_color_*`. `csh` / `tcsh` need `dircolors -c` (csh-syntax output) and `setenv`. macOS bash users hit the dual-trap: their `~/.bashrc` only runs on non-login shells, but Terminal.app spawns login shells — put env exports in `~/.bash_profile` (or `~/.zprofile` for the macOS default zsh).

shell / env
# ~/.bashrc (Linux) or ~/.bash_profile (macOS Terminal.app):
if [ -r ~/.dir_colors ]; then
    eval "$(dircolors -b ~/.dir_colors)"
else
    eval "$(dircolors -b)"
fi
alias ls='ls --color=auto'

# ~/.zshrc / ~/.zprofile (macOS default since 10.15):
if (( $+commands[dircolors] )); then
    eval "$(dircolors -b ~/.dir_colors 2>/dev/null || dircolors -b)"
fi
alias ls='ls --color=auto'

# ~/.config/fish/config.fish:
if command -q dircolors
    eval (dircolors -c ~/.dir_colors 2>/dev/null | sed 's/setenv/set -gx/')
end
alias ls 'ls --color=auto'

# Or use fish's native per-element colours instead of LS_COLORS:
set -gx fish_color_command       green
set -gx fish_color_param         normal
set -gx fish_color_quote         yellow
set -gx fish_color_error         red --bold
set -gx fish_color_autosuggestion 8a8a8a

# ~/.cshrc / ~/.tcshrc:
eval \`dircolors -c ~/.dir_colors\`
alias ls 'ls --color=auto'

# macOS double-trap reminder — these only run on LOGIN shell:
#   ~/.bash_profile, ~/.zprofile, ~/.profile
# These only run on NON-login (subshell) shell:
#   ~/.bashrc, ~/.zshrc
# Terminal.app spawns login shells; iTerm2 + VS Code terminal default
# to non-login. Source one from the other for symmetry:
#   echo '[ -r ~/.bashrc ] && . ~/.bashrc' >> ~/.bash_profile

Adjacent pages on this site that cover the surrounding ANSI / ls / pager ecosystem.