GREP_COLORS — ANSI colours for grep, ripgrep, ag
GNU `grep` reads the `GREP_COLORS` environment variable (plural, with S — replaced the legacy single-colour `GREP_COLOR` in grep 2.5.2, March 2007). It is a colon-separated string of `key=SGR` pairs that colours every output element independently: matched text, filename, line number, byte offset, separator, selected line, and context line. `ripgrep` does not read `GREP_COLORS` — it uses its own `--colors <type>:<attribute>:<value>` dotted syntax. `ag` (the_silver_searcher) uses dedicated `--color-line-number=` / `--color-match=` / `--color-path=` flags. Seven copy-paste snippets below cover the full map across all three tools, plus the canonical pipe-into-grep trap (colour bytes break naive substring matching).
Configuration snippets
GREP_COLOR (legacy, single colour) vs GREP_COLORS (current, per-slot)
GNU grep historically read one env var, `GREP_COLOR` (singular, no S), whose value was a single SGR string applied to matched text only — every other element (filename, line number, separator) was hard-coded. Since **GNU grep 2.5.2 (March 2007)** the canonical env is `GREP_COLORS` (plural, with S) — a colon-separated `key=SGR` string that colours every element of the output independently. `GREP_COLOR` is still read for backward compatibility but `GREP_COLORS=mt=...` overrides it. If both are set, `GREP_COLORS` wins. Stale `.bashrc` files often still pin `GREP_COLOR=01;31` for legacy reasons — replace it with the full `GREP_COLORS` form below.
# Legacy — single colour for matched text only (pre-2007 grep):
export GREP_COLOR='01;31' # bold red match, only
# Current — per-slot, colours every output element independently:
export GREP_COLORS='mt=01;31:fn=35:ln=32:bn=32:se=36'
# mt=01;31 matched text → bold red
# fn=35 filename prefix → magenta
# ln=32 line number prefix → green
# bn=32 byte offset prefix → green
# se=36 separator (: / -- ) → cyan
# Check which env is set:
env | grep -E '^GREP_COLOR'
# If both are set, GREP_COLORS wins — drop the legacy one to avoid confusion:
unset GREP_COLORGREP_COLORS — the colon-separated key=SGR slot syntax
A single colon-separated string of `key=value` pairs, one pair per output element. Keys come in three flavours: **two-letter slot tokens** (matched text, filename, line number, byte offset, separator, selected line, context line), **boolean toggles** (`rv` swaps selected vs context when `-v` is passed; `ne` skips the Erase-In-Line clearing escape — set it if your terminal has a buggy `\x1b[K` implementation), and **SGR-only values** (no leading `\x1b[` or trailing `m` — bare `01;31` = bold red, `38;5;208` = 256-palette orange, `38;2;255;128;0` = 24-bit truecolor). Boolean keys take NO `=value` (presence alone is the signal). Slots not listed fall back to grep's defaults: `mt=01;31`, `fn=35`, `ln=32`, `bn=32`, `se=36`, `sl=` empty, `cx=` empty.
# All 11 GREP_COLORS slots, set explicitly:
export GREP_COLORS='\
mt=01;31:\
ms=01;31:\
mc=01;31:\
fn=35:\
ln=32:\
bn=32:\
se=36:\
sl=:\
cx=:\
rv:\
ne'
# What each slot does:
# mt matched text on EITHER selected or context line — shorthand
# for setting ms + mc to the same value
# ms matched text on a selected line (default: 01;31 bold red)
# mc matched text on a context line (default: 01;31 bold red)
# fn filename prefix (default: 35 magenta)
# ln line number prefix (-n) (default: 32 green)
# bn byte offset prefix (-b) (default: 32 green)
# se separator (: between fn/ln/match; --
# between groups; - in context output) (default: 36 cyan)
# sl the entire selected (matching) line (default: empty = inherit)
# cx the entire context line (-A/-B/-C) (default: empty = inherit)
# rv boolean — swaps sl<->cx when -v (default: off)
# ne boolean — skip Erase-In-Line escape (default: off)
# Truecolor since grep 3.0+ (no real version gate — passes bytes through):
export GREP_COLORS='mt=38;2;255;128;0;01:fn=38;5;213:ln=38;5;120'
# mt=38;2;255;128;0;01 match → bold truecolor orange
# fn=38;5;213 filename → 256-palette pink
# ln=38;5;120 line number → 256-palette mint
# Verify by piping a small file through it:
printf 'one\ntwo\nthree\n' | grep --color=always two--color={auto,always,never} — the per-invocation override
`grep --color={auto,always,never}` (also accepted: `--colour`, the British spelling — both forms work). `auto` is the sensible default — colour on TTY, strip on pipe. `always` forces colour through pipes; `never` disables. **Default for `grep` with no flag is `never`** — most distros alias `grep` to `grep --color=auto` in `~/.bashrc` / `/etc/profile.d/colorls.sh` to flip this. `egrep` / `fgrep` / `rgrep` honour the same flag. `--color` accepts no value at all (`grep --color pattern`) and defaults to `auto` in that form. The universal `NO_COLOR=1` env opt-out is honoured since GNU grep 3.5 (2020) — overrides every form of `--color`.
# Three modes:
grep --color=auto pattern file # TTY only (the sensible default)
grep --color=always pattern file # force through pipes
grep --color=never pattern file # disable entirely
grep --color pattern file # bare flag = auto
NO_COLOR=1 grep pattern file # universal opt-out (grep 3.5+)
# Canonical aliases — drop into ~/.bashrc / ~/.zshrc:
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
# Force colour into a pager that renders SGR:
grep --color=always -rn pattern src/ | less -R
# Force colour into an HTML converter for blog / code review:
grep --color=always -rn TODO src/ | aha > todos.html
# Defeat the alias for one invocation (use the binary directly):
\\grep pattern file # bypasses alias
command grep pattern file # same
/usr/bin/grep pattern file # explicit path
# British spelling — same flag, same behaviour:
grep --colour=always pattern fileripgrep — --colors flag with the richer dotted syntax
`ripgrep` (`rg`, by BurntSushi — Rust-based, the modern replacement) does **NOT** read `GREP_COLORS`. Its own syntax uses repeated `--colors <type>:<attribute>:<value>` flags, where `type` is one of `path` / `line` / `column` / `match`, `attribute` is one of `fg` / `bg` / `style` / `none`, and `value` is a named colour (`red` / `green` / `blue` / `yellow` / `magenta` / `cyan` / `white` / `black`), `256` for the 256 palette, or `0xRRGGBB` for truecolor. `style` accepts `bold` / `intense` / `nobold` / `nointense` / `underline` / `nounderline`. Multiple `--colors` flags compose. Persist via `RIPGREP_CONFIG_PATH=$HOME/.ripgreprc`, one flag per line in that file. ripgrep also honours `--color={auto,always,never,ansi}` (the `ansi` variant always emits even on Windows where it would normally use the console API), and `NO_COLOR` out of the box.
# One-shot — override match colour to bright yellow on red background:
rg --colors 'match:fg:yellow' \
--colors 'match:bg:red' \
--colors 'match:style:bold' \
pattern src/
# All four element types:
rg --colors 'path:fg:magenta' \
--colors 'path:style:bold' \
--colors 'line:fg:green' \
--colors 'line:style:nointense' \
--colors 'column:fg:cyan' \
--colors 'match:fg:red' \
--colors 'match:style:bold' \
pattern src/
# Truecolor (0xRRGGBB form):
rg --colors 'match:fg:0xff8000' \
--colors 'match:style:bold' \
pattern src/
# 256 palette:
rg --colors 'match:fg:256' \
--colors 'match:fg:208' \
pattern src/
# Persist via ~/.ripgreprc (one flag per line, no quotes):
cat > ~/.ripgreprc <<'EOF'
--colors=match:fg:yellow
--colors=match:bg:red
--colors=match:style:bold
--colors=path:fg:magenta
--colors=line:fg:green
--smart-case
EOF
echo 'export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"' >> ~/.bashrc
# Color modes — note 'ansi' is the Windows-emit-anyway variant:
rg --color=auto pattern # default — TTY only
rg --color=always pattern # force through pipe (Unix-style ANSI)
rg --color=ansi pattern # force ANSI even on Windows (skip console API)
rg --color=never pattern # disable
NO_COLOR=1 rg pattern # honouredag (the_silver_searcher) — --color-* flags + AG_COLORS env
`ag` (the_silver_searcher, by ggreer — C-based, pre-dates ripgrep) takes a yet-different shape: three dedicated flags `--color-line-number=<SGR>`, `--color-match=<SGR>`, `--color-path=<SGR>`, each accepting a raw SGR-parameter string (no leading `\x1b[`, no trailing `m`). Defaults: `--color-line-number=1;33` (bold yellow), `--color-match=30;43` (black-on-yellow), `--color-path=1;32` (bold green). Persist via `~/.agrc` (one flag per line, no leading `--`) — ag reads it before parsing argv. Honours `--color` / `--nocolor` (no `=value` form, two separate flags) and the universal `NO_COLOR`. Note: ag does NOT support `path:fg:red`-style dotted syntax — only the raw SGR-number form. ag is no longer actively developed (last release 2021) but is still in widespread use because of its speed and the giant install base from before ripgrep took over.
# One-shot — override match colour to bright red bg + bold white fg:
ag --color-match='1;37;41' pattern src/
# All three element flags together:
ag --color-line-number='1;36' \
--color-match='1;37;41' \
--color-path='1;35' \
pattern src/
# 256-palette + truecolor (ag passes bytes through, no validation):
ag --color-match='38;5;208;01' pattern src/ # palette orange
ag --color-match='38;2;255;128;0;01' pattern src/ # 24-bit orange
# Toggle colour (no =value form for these flags):
ag --color pattern src/ # force on
ag --nocolor pattern src/ # force off
NO_COLOR=1 ag pattern src/ # universal opt-out (honoured)
# Persist via ~/.agrc — one flag per line, NO leading -- and NO quotes:
cat > ~/.agrc <<'EOF'
--color-line-number=1;36
--color-match=1;37;41
--color-path=1;35
--smart-case
--hidden
EOF
# Verify ag is picking it up:
ag --help 2>&1 | grep -E 'color|agrc'The pipe-into-grep trap — colour bytes break naive substring matching
A classic real-world trap: `cmd --color=always | grep something` (or `ls --color=always | grep .conf`, or `git log --color=always | grep TODO`) **fails silently** because the SGR bytes are now part of every byte stream `grep` sees — they wrap matched substrings in the upstream output. Your `grep something` is now looking for the literal byte sequence `s` `o` `m` `e` `t` `h` `i` `n` `g` but the input contains `s` `\x1b[1;31m` `o` `m` `e` `\x1b[0m` `t` `h` `i` `n` `g` (or some similar fragmentation). Three correct workarounds: (1) `--color=never` on the inner grep, (2) strip SGR with `sed` first, (3) use `--color=auto` (the default) and trust the TTY detection. Don't silently absorb SGR into grep input.
# WRONG — the inner grep sees SGR bytes split across the substring,
# the match silently fails:
ls --color=always | grep '.conf'
git log --color=always --oneline | grep 'TODO'
# RIGHT (1) — inner grep with --color=never strips colour BEFORE matching:
ls --color=always | grep --color=never '.conf'
git log --color=always --oneline | grep --color=never 'TODO'
# RIGHT (2) — strip SGR with sed before the inner grep sees it:
ls --color=always | sed -E 's/\x1b\[[0-9;]*m//g' | grep '.conf'
# RIGHT (3) — use --color=auto (the default) and trust TTY detection,
# the OUTER command will skip colour because the pipe is not a TTY:
ls --color=auto | grep '.conf' # ls strips colour automatically
git log --oneline | grep 'TODO' # git pager also off when piped
# Symptom check — if you suspect SGR is leaking into a grep, decode it:
cmd --color=always | head -c 200 | cat -v
# look for ^[[31m / ^[[0m sequences — those are SGR bytes
# General-purpose ansi-strip wrapper (drop into ~/.bashrc):
strip_ansi() { sed -E 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\][^\x07]*\x07//g'; }
# Usage: ls --color=always | strip_ansi | grep '.conf'Per-shell wire-up — bash · zsh · fish · csh
`GREP_COLORS` is a plain env var; any shell that exports env vars can set it. The wire-up differs per shell, and there is the same macOS bash double-trap as `/ls-colors` — `~/.bashrc` runs only on non-login shells, but Terminal.app spawns login shells, so env exports go in `~/.bash_profile` (or `~/.zprofile` for macOS default zsh). For fish, `set -gx` is the global-export form. For ripgrep, `RIPGREP_CONFIG_PATH` env points at a `.ripgreprc` file (any shell). For ag, `~/.agrc` is auto-read by the binary itself — no env var. The recipe below sets all three tools consistently with a coherent palette so output across `grep` / `rg` / `ag` looks the same.
# ~/.bashrc (Linux) or ~/.bash_profile (macOS Terminal.app):
export GREP_COLORS='mt=01;33;41:fn=35:ln=32:bn=32:se=36'
export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
# ~/.zshrc / ~/.zprofile (macOS default since 10.15):
export GREP_COLORS='mt=01;33;41:fn=35:ln=32:bn=32:se=36'
export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"
alias grep='grep --color=auto'
# ~/.config/fish/config.fish — set -gx is global-export:
set -gx GREP_COLORS 'mt=01;33;41:fn=35:ln=32:bn=32:se=36'
set -gx RIPGREP_CONFIG_PATH ~/.ripgreprc
alias grep 'grep --color=auto'
# ~/.cshrc / ~/.tcshrc — setenv, no = sign:
setenv GREP_COLORS 'mt=01;33;41:fn=35:ln=32:bn=32:se=36'
setenv RIPGREP_CONFIG_PATH ~/.ripgreprc
alias grep 'grep --color=auto'
# ~/.ripgreprc — one flag per line, no quotes:
cat > ~/.ripgreprc <<'EOF'
--colors=match:fg:yellow
--colors=match:bg:red
--colors=match:style:bold
--colors=path:fg:magenta
--colors=line:fg:green
EOF
# ~/.agrc — same one-flag-per-line, NO leading -- in some versions
# (always test with: ag --color-match='1;33' pattern . 2>&1 | head):
cat > ~/.agrc <<'EOF'
--color-line-number=1;36
--color-match=1;33;41
--color-path=1;35
EOF
# 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_profileGREP_COLORS slot reference
Every GREP_COLORS slot key, what it means, the GNU grep default value, and the grep flag combination that triggers it. Two boolean keys at the bottom (`rv`, `ne`) take no value — presence alone is the signal.
| Key | Meaning | Default | Used when |
|---|---|---|---|
| mt | matched text on either selected or context line (shorthand for ms+mc) | 01;31 | always when -v not set |
| ms | matched text on a selected (matching) line | 01;31 | default grep output |
| mc | matched text on a context line | 01;31 | with -A / -B / -C / -v |
| fn | filename prefix | 35 | with -H or recursive (-r) |
| ln | line number prefix | 32 | with -n |
| bn | byte offset prefix | 32 | with -b |
| se | separator (: between fn/ln/match; -- between groups; - in context) | 36 | always (when any prefix shown) |
| sl | entire selected (matching) line (excluding mt match) | (empty = inherit) | default grep output |
| cx | entire context line (excluding mt match) | (empty = inherit) | with -A / -B / -C |
| rv | BOOLEAN — swap sl<->cx roles when -v (invert match) is in effect | off | with -v |
| ne | BOOLEAN — skip the Erase-In-Line `\x1b[K` escape (workaround for buggy terms) | off | rare workaround only |
Related references
Adjacent pages on this site that cover the surrounding ANSI / pager / matcher ecosystem.
- Git colour configCompanion per-tool page — git's color.ui / per-command keys / slot palette / core.pager + LESS=FRX / piping --color=always into less -R.
- LS_COLORS env varCompanion per-tool page — LS_COLORS syntax (type + extension tokens), dircolors workflow, BSD LSCOLORS divergence, eza / exa.
- Strip ANSI codesAfter-the-fact regex to remove SGR bytes from a captured `grep --color=always` capture — useful when archiving log output.
- Common ANSI pitfallsSymptom → cause → fix for adjacent issues — colour bytes break grep substring matching, `--color=always` reaches the wrong consumer, NO_COLOR not honoured by old tools.