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.
# 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_mdLESS=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.
# 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' | headMANPAGER 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.
# 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 textLESSOPEN / 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.
# 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.
# 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 UIdiff-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.
# 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 | deltaPer-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.
# ~/.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, ~/.zshrcLESS_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.
| Env var | Paired with | Meaning | Used for |
|---|---|---|---|
| LESS_TERMCAP_md | me | start bold | man-page keywords / command names / headings |
| LESS_TERMCAP_me | md/mb/so | end bold / blink / standout (single reset) | shared reset for the three start sequences |
| LESS_TERMCAP_us | ue | start underline (italic in man) | man-page argument names / file names / emphasis |
| LESS_TERMCAP_ue | us | end underline | paired reset for the underline start sequence |
| LESS_TERMCAP_so | se | start standout (inverted highlight) | status / prompt line at the bottom of less; search match highlights |
| LESS_TERMCAP_se | so | end standout | paired reset for the standout start sequence |
| LESS_TERMCAP_mb | me | start blink (rare) | rarely used; copy from md so blinking content stays visible |
Related references
Adjacent pages on this site that cover the surrounding ANSI / pager / man / diff ecosystem.
- Git colour configCompanion per-tool page — git's color.ui / per-command keys / slot palette / core.pager + LESS=FRX (the same LESS env this page covers).
- LS_COLORS env varCompanion per-tool page — LS_COLORS syntax, dircolors workflow, BSD LSCOLORS divergence, eza / exa.
- GREP_COLORS env varCompanion per-tool page — GREP_COLORS slot syntax, ripgrep --colors dotted syntax, ag --color-* flags, the pipe-into-grep trap.
- Common ANSI pitfallsSymptom → cause → fix for adjacent issues — less strips SGR by default, MANPAGER not honoured, groff-SGR vs overstrike path confusion.