Skip to main content
ansicode

LESS colours — coloured man pages via LESS_TERMCAP_*, LESS=R, MANPAGER

`less` reads `LESS_TERMCAP_*` environment variables to colourise the bold / underline / standout sequences it sees from `groff` overstrike — that is the canonical recipe for coloured `man` pages. The other half is `LESS=R` (or `LESS='-R -F -X'`) which lets SGR bytes already in the upstream pipe (from `git log --color=always`, `ls --color=always`, `grep --color=always`) pass THROUGH less to the terminal rather than being stripped. Seven copy-paste snippets below cover the full map: the canonical 6-var LESS_TERMCAP_* set, the LESS env flags, the MANPAGER + MANROFFOPT + GROFF_NO_SGR interaction, lesspipe / LESSOPEN preprocessing, bat as a colourised MANPAGER, diff-so-fancy / delta as supercharged git pagers, and per-shell wire-up.

Configuration snippets

LESS_TERMCAP_* — the canonical 6-var set that colourises man pages

`less` reads `LESS_TERMCAP_*` environment variables to **override** the terminfo entries it would normally use for bold / underline / standout. When `man` invokes `less` to page output, the nroff produced by `groff` uses backspace-overstrike (`b\bbo\bol\bld\bd` = bold) and underscore-overstrike (`_\bb_\bo_\bl_\bd` = italic / argument names) — `less` interprets these and emits the configured `LESS_TERMCAP_*` bytes. Six vars matter: `md` (start bold), `me` (end bold / blink / standout), `us` (start underline), `ue` (end underline), `so` (start standout — used for the status / prompt line and search matches), `se` (end standout). Optional `mb` (start blink) is usually copied from `md` so blinking does not get hidden. The values are RAW ANSI / VT escape bytes — write the literal `\e[01;31m` form in your shell config.

shell / env
# The canonical 6-var set — drop into ~/.bashrc / ~/.zshrc verbatim.
# Bold red for keywords / commands (md), green underline for arguments
# (us), yellow standout for status line + search matches (so).

export LESS_TERMCAP_md=$'\e[01;31m'      # start bold        (man: keywords)
export LESS_TERMCAP_me=$'\e[0m'          # end bold / blink / standout
export LESS_TERMCAP_us=$'\e[01;32m'      # start underline   (man: args / files)
export LESS_TERMCAP_ue=$'\e[0m'          # end underline
export LESS_TERMCAP_so=$'\e[01;44;33m'   # start standout    (status / search hit)
export LESS_TERMCAP_se=$'\e[0m'          # end standout
export LESS_TERMCAP_mb=$'\e[01;31m'      # start blink — copy md so it stays visible

# Verify they are exported with the right bytes (decode the ESC):
env | grep LESS_TERMCAP | cat -v
#   should show ^[[01;31m etc — the ^[ is ESC (0x1b), the rest is the SGR

# See the effect immediately — load a man page:
man ls

# Quick a/b test — unset, reload, compare:
( unset LESS_TERMCAP_md LESS_TERMCAP_me LESS_TERMCAP_us \
        LESS_TERMCAP_ue LESS_TERMCAP_so LESS_TERMCAP_se; \
  man ls )
# vs the current shell session with the vars set.

# Note the $'...' syntax — bash / zsh ANSI-C quoting that turns \e into
# the literal ESC byte. If your shell does NOT support $'...' (older sh /
# dash) use printf:
#   LESS_TERMCAP_md=$(printf '\e[01;31m'); export LESS_TERMCAP_md

LESS=R — colour passthrough when upstream emits raw SGR directly

`LESS_TERMCAP_*` covers the case where `less` itself adds colour by INTERPRETING the input. The other case is when upstream **already emits SGR bytes** (e.g. `git log --color=always`, `ls --color=always`, `grep --color=always`, `colordiff`, `diff-so-fancy`) — those bytes need to pass THROUGH `less` to the terminal. By default `less` strips control characters and shows `^[[31m` literal noise instead. The fix is the `-R` flag (or `--RAW-CONTROL-CHARS`), which lets SGR bytes through but still cleans other control characters. The `LESS` env var sets default flags applied to every `less` invocation — `LESS=R` makes raw-control-chars the global default. Modern `less` (since v530, 2017) also supports `-r` (lowercase) which passes EVERY control char unchanged — use `-R` not `-r` unless you specifically need other control chars rendered.

shell / env
# Set the default once — every less invocation gains -R:
export LESS=R

# Optional companion flags many users add to LESS:
export LESS='-R -F -X'
#   -R   keep SGR bytes (already covered above)
#   -F   quit if output fits one screen (skip the pager-trap on short files)
#   -X   skip the alt-screen restore (so scrollback retains the pager content)

# These are the same flags git auto-sets when invoking its DEFAULT pager.
# Custom pagers (core.pager, PAGER, MANPAGER) do NOT inherit them — set
# LESS globally instead.

# One-shot — no env, flags on the command line:
git log --color=always --oneline | less -R
grep --color=always -rn TODO src/    | less -R
ls --color=always -la                 | less -R

# Without -R (the default), you see literal noise:
ls --color=always | less
#   src/  app/  ESC[01;34mlib^[[0mESC[m  ...  (ugly)

# With -R, colour passes through:
ls --color=always | less -R
#   src/  app/  (bold blue lib)  ...        (correct)

# Verify LESS is honoured by the binary:
echo "$LESS"
less --help 2>&1 | grep -E 'RAW|environment' | head

MANPAGER vs PAGER — modern man honours both, but MANPAGER wins

`man` looks up its pager in this order: `$MANPAGER`, then `$PAGER`, then the compile-time default (typically `less`). Setting `MANPAGER` lets you give `man` a different pager than the one you use for everything else — useful when you want `man` to always force colour-rendering flags but leave plain `less` invocations alone. The canonical safe value is `MANPAGER='less -R'`. On distros where `groff` is recent enough (1.18.1+, 2008) and `MANROFFOPT=-c` is set, `groff` emits SGR directly INSTEAD of backspace-overstrike — at that point `LESS_TERMCAP_*` stops applying (less is no longer interpreting overstrike), and you need `MANPAGER='less -R'` for the SGR to pass through. Modern Debian / Ubuntu / Arch ship this configuration by default; the BSDs and older RHEL still ship the overstrike path. Set both — `MANPAGER='less -R'` AND the `LESS_TERMCAP_*` vars — to cover both paths transparently.

shell / env
# Set both — covers groff-SGR path AND backspace-overstrike path:
export MANPAGER='less -R'
export MANROFFOPT='-c'              # tell groff to emit SGR (modern groff)

# Plus the LESS_TERMCAP_* vars from snippet 1 (for the overstrike path).

# Check which path your distro uses — run man on any page, then in less
# press 'v' to drop into $EDITOR (or use !cat) and look at the raw bytes:
#
#   If you see "b\bbo\bol\bld" overstrike → backspace-overstrike path,
#                                              LESS_TERMCAP_* is in play.
#   If you see "\x1b[1mbold\x1b[0m"        → groff-SGR path,
#                                              MANPAGER='less -R' is what matters.

# Force the overstrike path even on a modern distro (so LESS_TERMCAP_*
# applies again — useful when debugging custom palettes):
export GROFF_NO_SGR=1

# Test:
man ls
# Try changing LESS_TERMCAP_md to a glaring colour and reload to confirm
# which path is active:
export LESS_TERMCAP_md=$'\e[01;45m'        # purple background
man ls    # if you see purple text → overstrike path; else → SGR path

# When piping man output (e.g. into grep / a file) the formatting is
# normally stripped because the pager is not a TTY. Preserve it with:
MAN_KEEP_FORMATTING=1 man ls | head -50    # keeps overstrike bytes
# Then strip later with col -b:
MAN_KEEP_FORMATTING=1 man ls | col -b | head -50    # plain text

LESSOPEN / lesspipe — preprocess input before less sees it

`less` runs `$LESSOPEN` (and optionally `$LESSCLOSE`) on every input file to give the user a chance to transform the bytes before they reach the pager — most distros ship `lesspipe.sh` (Debian / Ubuntu) or `lesspipe.lua` (Arch) and wire it via `eval "$(lesspipe)"` in `/etc/profile.d/less.sh`. Out of the box this lets `less archive.tar.gz` show a directory listing, `less foo.pdf` show extracted text, `less foo.deb` show package metadata — much wider input support than `less` alone. The colour angle: `lesspipe` integrates with `source-highlight` (or `highlight`, `bat`, `pygmentize`) so `less foo.py` shows the Python source with syntax highlighting. Configure the highlighter via `LESSOPEN='| /usr/bin/src-hilite-lesspipe.sh %s'` and add `LESS=" -R"` (note leading space) to keep colour passthrough on.

shell / env
# Default Debian / Ubuntu setup (already in /etc/profile.d/less.sh):
export LESSOPEN="| /usr/bin/lesspipe %s"
export LESSCLOSE="/usr/bin/lesspipe %s %s"

# Verify it's working — should show metadata not raw bytes:
less /usr/lib/x86_64-linux-gnu/libc.so.6      # ELF binary → strings dump
less /etc/os-release                          # text file → straight through
less ./some.tar.gz                            # archive → tar tvfz listing

# Add syntax highlighting for source code via source-highlight:
sudo apt install source-highlight              # Debian / Ubuntu
brew install source-highlight                  # macOS

# Wire it as the pre-processor — note leading space in LESS:
export LESSOPEN="| /usr/bin/src-hilite-lesspipe.sh %s"
export LESS=" -R"                              # leading space prevents typoed
                                               # binary name issues

# Test it:
less app/page.tsx                              # should show syntax-highlighted TS
less script.sh                                 # should show syntax-highlighted bash
less /etc/nginx/nginx.conf                     # should show coloured nginx config

# Disable for one invocation (debugging):
LESSOPEN= less raw.log
less --no-lessopen raw.log                     # explicit flag, less v530+

# Combine with bat for richer highlighting (snippet 5 explores bat more):
export LESSOPEN="| /usr/bin/bat --color=always --paging=never %s"

bat / batcat — a colourised less replacement with syntax highlighting

`bat` (by sharkdp — Rust) is a `cat` / `less` hybrid that adds syntax highlighting, line numbers, Git modification markers, and a paging mode that respects `less` keybindings (`bat -p` for non-paging). It works as a drop-in `cat` replacement (`alias cat=bat`) AND as a MANPAGER for colourised man pages with syntax-highlighted code blocks. On Debian / Ubuntu the binary is renamed `batcat` (an older unrelated `bat` package squats on the name) — Arch / Fedora / Homebrew ship the binary as `bat`. Themes via `bat --list-themes` (`OneHalfDark` / `Dracula` / `gruvbox-dark` / `Solarized (light)` / `ansi` for terminal-palette-only). `BAT_THEME` env sets the default; `BAT_PAGER='less -R'` overrides the pager. `--color={auto,always,never}` and `NO_COLOR` honoured.

shell / env
# Install:
#   Arch:        pacman -S bat
#   Debian/Ubuntu: apt install bat   (binary is "batcat", not "bat")
#   Fedora:      dnf install bat
#   macOS:       brew install bat

# Debian / Ubuntu workaround — alias batcat back to bat:
alias bat='batcat'

# Use as a cat replacement (paging only when output exceeds terminal):
bat app/page.tsx                              # syntax-highlighted, line numbers
bat -p app/page.tsx                           # plain mode (no header, no pager)
bat -A binary.dat                             # show non-printable as escapes

# Use as MANPAGER for colourised man pages:
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
#   col -bx       strip backspace-overstrike (otherwise bat sees garbage)
#   bat -l man    syntax = man (highlights options + section refs)
#   bat -p        plain mode (no header)

# Theme + style configuration:
export BAT_THEME='OneHalfDark'                # any theme from --list-themes
export BAT_STYLE='numbers,changes,header'     # opt-in column set
export BAT_PAGER='less -R'                    # override default pager

# Verify which binary, where the config file lives:
bat --version
bat --config-file                              # path; create + edit to persist
bat --generate-config-file                     # write a fully-commented template

# Pipe coloured input into bat (preserve SGR):
git log --color=always --oneline | bat --paging=always
diff -u old.txt new.txt | bat -l diff

# Use as drop-in cat (with caveats — bat exits with paging on long output):
alias cat='bat -p'                             # plain mode = no extra UI

diff-so-fancy / delta — supercharged coloured diff pagers

Two popular pager-replacements specifically for git / diff output, both of which assume `LESS=" -R"` (or `LESS=FRX`) is set. **`diff-so-fancy`** (by so-fancy — Perl) cleans up unified-diff output: removes the `+++ / ---` headers, replaces `@@` hunk markers with a thin separator, and recolours `+ / -` lines with brighter SGR. Wire it via `git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"`. **`delta`** (by dandavison — Rust) goes further: side-by-side mode, syntax-highlighted code, configurable line numbers + plus/minus markers, Git-blame integration. Wire it via `git config --global core.pager delta` (delta auto-detects it is the pager and applies). `delta` reads its config from `~/.gitconfig` `[delta]` section — set `features = side-by-side line-numbers decorations` for the modern look. Both honour `NO_COLOR` and `--color=never`.

shell / env
# Install:
#   diff-so-fancy: brew install diff-so-fancy   (or npm i -g diff-so-fancy)
#   delta:         brew install git-delta       (or cargo install git-delta)

# diff-so-fancy — wire into git core.pager:
git config --global core.pager 'diff-so-fancy | less --tabs=4 -RFX'
git config --global interactive.diffFilter 'diff-so-fancy --patch'

# Optional colour tuning that diff-so-fancy assumes:
git config --global color.diff-highlight.oldNormal    'red bold'
git config --global color.diff-highlight.oldHighlight 'red bold 52'
git config --global color.diff-highlight.newNormal    'green bold'
git config --global color.diff-highlight.newHighlight 'green bold 22'
git config --global color.diff.meta       'yellow'
git config --global color.diff.frag       'magenta bold'
git config --global color.diff.commit     'yellow bold'
git config --global color.diff.old        'red bold'
git config --global color.diff.new        'green bold'
git config --global color.diff.whitespace 'red reverse'

# delta — wire it as the pager (auto-detects):
git config --global core.pager delta
git config --global interactive.diffFilter 'delta --color-only'
git config --global delta.navigate true
git config --global delta.light false
git config --global delta.line-numbers true
git config --global delta.side-by-side true
git config --global delta.syntax-theme 'OneHalfDark'

# Disable for one command:
git --no-pager diff
git -c core.pager=cat diff

# Test:
git diff HEAD~1
git log --oneline -p | head -50

# Pipe a raw diff (not via git) into either tool:
diff -u old.txt new.txt | diff-so-fancy | less -R
diff -u old.txt new.txt | delta

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

The canonical setup combines all the snippets above: `LESS_TERMCAP_*` for man-page colour, `LESS='-R -F -X'` for SGR passthrough + sane quitting + scrollback retention, `MANPAGER='less -R'` to flow SGR from modern groff, optionally `BAT_THEME` for syntax-highlighted code. Each shell exports differently. The macOS bash double-trap from `/ls-colors` and `/grep-colors` applies here too — `~/.bashrc` only runs on non-login shells but Terminal.app spawns login shells, so env exports go in `~/.bash_profile` (or `~/.zprofile` for macOS default zsh). Fish needs the literal-ESC byte via the printf-into-set pattern because `$'\e'` ANSI-C quoting is bash / zsh syntax only.

shell / env
# ~/.bashrc (Linux) or ~/.bash_profile (macOS Terminal.app):
export LESS_TERMCAP_md=$'\e[01;31m'     # bold        → bold red
export LESS_TERMCAP_me=$'\e[0m'
export LESS_TERMCAP_us=$'\e[01;32m'     # underline   → bold green
export LESS_TERMCAP_ue=$'\e[0m'
export LESS_TERMCAP_so=$'\e[01;44;33m'  # standout    → bold yellow-on-blue
export LESS_TERMCAP_se=$'\e[0m'
export LESS_TERMCAP_mb=$'\e[01;31m'
export LESS='-R -F -X'
export MANPAGER='less -R'
export MANROFFOPT='-c'
export PAGER='less'

# ~/.zshrc / ~/.zprofile (same syntax, $'...' works in zsh too):
export LESS_TERMCAP_md=$'\e[01;31m'
# ... (same six exports)
export LESS='-R -F -X'
export MANPAGER='less -R'

# ~/.config/fish/config.fish — fish has NO $'...' quoting, use printf:
set -gx LESS_TERMCAP_md (printf '\e[01;31m')
set -gx LESS_TERMCAP_me (printf '\e[0m')
set -gx LESS_TERMCAP_us (printf '\e[01;32m')
set -gx LESS_TERMCAP_ue (printf '\e[0m')
set -gx LESS_TERMCAP_so (printf '\e[01;44;33m')
set -gx LESS_TERMCAP_se (printf '\e[0m')
set -gx LESS_TERMCAP_mb (printf '\e[01;31m')
set -gx LESS '-R -F -X'
set -gx MANPAGER 'less -R'

# ~/.cshrc / ~/.tcshrc — printf inside backticks, setenv (no = sign):
setenv LESS_TERMCAP_md "`printf '\e[01;31m'`"
setenv LESS_TERMCAP_me "`printf '\e[0m'`"
# ... (same six setenv)
setenv LESS '-R -F -X'
setenv MANPAGER 'less -R'

# Verify:
man ls          # should show coloured keywords + underlined args
echo "$LESS"    # should print "-R -F -X"
env | grep -E 'LESS_TERMCAP|MANPAGER' | cat -v   # show with ^[ visible

# macOS double-trap reminder — these only run on LOGIN shell:
#   ~/.bash_profile, ~/.zprofile, ~/.profile
# These only run on NON-login (subshell) shell:
#   ~/.bashrc, ~/.zshrc

LESS_TERMCAP_* slot reference

The seven LESS_TERMCAP_* env vars — what each starts or ends, the paired start/end variable, and the role less assigns to it. The me reset is shared by md / mb / so; ue pairs only with us; se pairs only with so.

LESS_TERMCAP_* slot reference
Env varPaired withMeaningUsed for
LESS_TERMCAP_mdmestart boldman-page keywords / command names / headings
LESS_TERMCAP_memd/mb/soend bold / blink / standout (single reset)shared reset for the three start sequences
LESS_TERMCAP_usuestart underline (italic in man)man-page argument names / file names / emphasis
LESS_TERMCAP_ueusend underlinepaired reset for the underline start sequence
LESS_TERMCAP_sosestart standout (inverted highlight)status / prompt line at the bottom of less; search match highlights
LESS_TERMCAP_sesoend standoutpaired reset for the standout start sequence
LESS_TERMCAP_mbmestart blink (rare)rarely used; copy from md so blinking content stays visible

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