TechEarl

How to grep Case-Insensitively (grep -i)

grep -i 'pattern' file matches regardless of case. The flag pairs with -r, -w, -v, and -c the way you would expect, but -i only folds ASCII case reliably. Non-ASCII case folding (accented characters, the Turkish dotted-i) depends on your locale. The combinations, the locale caveat, the PCRE per-pattern (?i) flag, and the BSD vs GNU differences.

Ishan KarunaratneIshan Karunaratne⏱️ 12 min readUpdated
How to grep case-insensitively with grep -i. Combine -i with -r, -w, -v, -c, the locale caveat for non-ASCII case folding, the PCRE (?i) inline flag, and BSD vs GNU grep differences.

grep -i 'pattern' file matches the pattern regardless of case. With -i, error, Error, ERROR, and eRRoR all match the same search. It is one of the most-used grep flags because log files, error messages, and code identifiers are rarely consistent about capitalization.

The flag is short for --ignore-case. It works in every regex mode (grep, grep -E, grep -F, grep -P) and combines cleanly with the other flags you reach for daily. The one thing it does not do reliably is fold case outside ASCII, which is the gotcha that catches people searching non-English text. That caveat gets its own section below.

Set your values

Try it with your own values

Set your OS, search path, and pattern. Every grep example below updates with your values.

The one-liner

bash· Linux (GNU)
grep -i ':pattern' app.log

That prints every line in app.log that contains :pattern in any mix of upper and lower case. Note the PowerShell line has no extra flag: Select-String is case-insensitive by default, the opposite of grep. To make Select-String case-sensitive you add -CaseSensitive.

The pattern is quoted with single quotes so the shell does not expand or interpret it. Quote your grep patterns always, even when they look like plain words today, because the next edit might add a * or a $.

Combining -i with the flags you already use

-i stacks with the rest of grep's flag set. The combinations below are the ones I type most often.

The single most useful combination. Search an entire directory tree, ignoring case.

bash· Linux (GNU)
grep -rin ':pattern' :search_path

-r is recursive, -i ignores case, -n prints line numbers. This is the everyday "find every mention of this thing anywhere in the project" search. Add -I (capital i, distinct from -i) to skip binary files, which both GNU and BSD grep support.

-i with -w: whole word, any case

-w constrains the match to whole words, so :pattern does not match a substring of a longer identifier. Combined with -i, you get a case-insensitive whole-word search.

bash· Linux (GNU)
grep -iw ':pattern' app.log

Without -w, searching for error also matches errored, terror, and error_handler. With -iw, only the standalone word error, Error, or ERROR matches. PowerShell has no -w flag, so the word boundaries go into the regex as \b. See grep whole word matching for the full word-boundary reference.

-i with -v: invert, ignoring case

-v inverts the match: it prints lines that do not match. With -i, the exclusion is case-insensitive too.

bash· Linux (GNU)
grep -iv ':pattern' app.log

This prints every line that has no :pattern in it, regardless of how it was capitalized. Useful for trimming a known-noisy string out of output: grep -iv 'debug' drops every line mentioning debug in any case.

-i with -c: count matches, ignoring case

-c prints a count of matching lines instead of the lines themselves.

bash· Linux (GNU)
grep -ic ':pattern' app.log

One subtlety: -c counts matching lines, not total matches. A line with :pattern mentioned three times counts once. To count every occurrence including repeats on a line, use grep -io ':pattern' app.log | wc -l, where -o prints each match on its own line.

Case-insensitive recursive search across a codebase

Putting the combination to work: find every case-insensitive mention of a string in a project, with line numbers, skipping node_modules and .git.

bash· Linux (GNU)
grep -rin --exclude-dir=node_modules --exclude-dir=.git ':pattern' :search_path

GNU grep has --exclude-dir; BSD grep on macOS does not, so the mac variant pipes find into xargs. If you live on macOS and want the GNU flags, brew install grep gives you GNU grep as ggrep. The grep cheat sheet covers the filename-filtering patterns in more depth.

The locale and UTF-8 caveat

This is the part most -i tutorials skip. -i reliably folds ASCII case only. For the 26 Latin letters A-Z, -i always treats upper and lower as equivalent. Outside ASCII, case folding depends on your locale.

The locale is set by environment variables, chiefly LC_ALL, LC_CTYPE, and LANG. Check it with:

bash
locale

If your locale is C or POSIX (common on minimal containers and stripped-down servers), grep does no non-ASCII case folding at all. With LC_CTYPE=C, grep -i 'eacute' will not match an accented E-acute against a lowercase e-acute, because the C locale knows nothing about accented characters.

A UTF-8 locale such as en_US.UTF-8 enables Unicode-aware case folding for accented Latin characters, Greek, Cyrillic, and most scripts that have a case distinction. To force it for one command:

bash
LC_ALL=en_US.UTF-8 grep -i 'pattern' file.txt

Two specific traps worth naming:

  • The Turkish dotted-i. In a Turkish locale (tr_TR.UTF-8), uppercase I lowercases to a dotless i, and lowercase i uppercases to a dotted capital. So grep -i 'I' behaves differently under tr_TR.UTF-8 than under en_US.UTF-8. If a script searches for INDEX and runs on a Turkish-locale host, the match for index can silently fail. This is the canonical case-folding bug.
  • The C locale on containers. Docker base images frequently ship with LANG unset, which defaults to the C locale. A grep -i that worked on your dev machine can quietly stop folding non-ASCII case inside the container. If a pipeline depends on non-ASCII matching, set LANG explicitly in the image.

For pure ASCII text, none of this matters and -i just works. The caveat only bites once your data has accented or non-Latin characters.

Per-pattern case control with PCRE (?i)

-i is all-or-nothing: it applies to the entire pattern. Sometimes you want part of a pattern case-insensitive and the rest case-sensitive. PCRE mode (grep -P, GNU only) supports the inline (?i) flag for exactly this.

The inline flag turns case-insensitivity on from that point in the pattern onward:

bash· Linux (GNU)
grep -P '(?i):pattern' app.log

You can also scope it to a group with (?i:...), which makes only that group case-insensitive and leaves the rest of the pattern case-sensitive. For example, a pattern that matches the word log in any case but requires the rest to be exact uses (?i:log) followed by the case-sensitive part. PowerShell's .NET regex engine understands (?i) and (?i:...) the same way, which is why the Windows variant above pairs -CaseSensitive with an inline (?i) to flip just the relevant span.

(?i) is the right tool when -i is too blunt. If you only ever need whole-pattern folding, plain -i is simpler and works in every mode.

macOS BSD grep vs GNU grep

-i itself is identical on both. The differences are in the flags you combine it with.

BehaviorGNU grepBSD grep (macOS default)
-i (ignore case)SupportedSupported, identical
--ignore-case long formSupportedSupported
-P (PCRE, needed for (?i))SupportedNot supported
(?i) inline flagWorks under -PNo -P, so unavailable
--exclude-dir (for -ri searches)SupportedNot supported
Non-ASCII case foldingLocale-dependent (UTF-8 locale)Locale-dependent (UTF-8 locale)
-w whole wordSupportedSupported

The practical takeaway: grep -i, grep -ri, grep -iw, grep -iv, and grep -ic all work the same on macOS and Linux. Only the PCRE-dependent (?i) and the --exclude-dir convenience flag are GNU-only. For those, brew install grep and use ggrep.

Common mistakes

1. Assuming -i folds non-ASCII case. It only reliably folds ASCII A-Z. Accented characters, Greek, Cyrillic, and other scripts depend on a UTF-8 locale. On a C-locale host (many containers), non-ASCII -i does nothing. Check locale before trusting a non-English -i search.

2. Forgetting the locale on containers. A grep -i that works locally can stop folding non-ASCII case inside Docker, where LANG is often unset. Set LANG in the image if the pipeline needs it.

3. The Turkish-i surprise. Under tr_TR.UTF-8, I and i do not fold to each other the way they do in English locales. Scripts that search for uppercase identifiers can fail on Turkish-locale hosts. Force LC_ALL=C or LC_ALL=en_US.UTF-8 for ASCII identifier searches that must be deterministic.

4. Adding -i to a pattern that already spans both cases. A pattern like [Ee]rror already matches Error and error. Adding -i on top is redundant and, worse, it also makes any other letters in the pattern case-insensitive, which may not be what you intended. Pick one mechanism: either -i for the whole pattern, or an explicit character class, not both.

5. Confusing -i with -I. Lowercase -i ignores case. Uppercase -I skips binary files. They are unrelated. grep -rI skips binaries; grep -ri ignores case; grep -riI does both.

6. Expecting -c to count occurrences. -c counts matching lines. Three matches on one line count as one. Use grep -io 'pattern' | wc -l for a true occurrence count.

When NOT to use -i

-i is the wrong choice when case is meaningful data:

  • Matching constants versus variables. In most languages, MAX_RETRIES is a constant and maxRetries is a variable. A case-insensitive search collapses the two. When you specifically want the SCREAMING_SNAKE_CASE constant, search case-sensitively so the lowercase variable does not pollute the results.
  • Distinguishing types from instances. User the class and user the instance variable are different things. Refactoring the class name with a case-insensitive grep will sweep up every instance variable too.
  • Environment variables versus values. PATH the variable and path in a comment are unrelated. A case-sensitive search keeps configuration audits accurate.
  • Acronyms and identifiers in code review. When you grep a codebase to find every definition of API or URL, case sensitivity is usually what you want, because the lowercase forms appear inside hundreds of unrelated identifiers.

The rule of thumb: use -i for human-written prose (logs, messages, documentation) where capitalization is inconsistent and meaningless. Drop -i for code identifiers, where capitalization is a deliberate signal.

See also

FAQ

TagsgrepCLILinuxmacOSBSDRegexShell Scripting
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Use grep 'pattern' file | awk '{print $2}' to filter lines and print a specific column. awk field basics, custom separators with -F, multi-column output, grep -o and cut alternatives, and when awk alone replaces the pipe.

How to grep and Print a Specific Column (grep + awk)

grep filters lines, awk extracts fields. The classic pipe is grep 'pattern' file | awk '{print $2}'. This covers awk field basics ($1, $NF), custom separators with -F, multi-column output, the cases grep -o and cut cover on their own, and the fact that awk's own pattern match makes the grep half optional.

Use grep -r 'pattern' . to search every file in a directory tree. The -r vs -R symlink difference, --include and --exclude-dir filters, -rl and -rn, and the macOS BSD vs GNU grep gaps.

How to grep Recursively Through a Directory

grep -r 'pattern' . searches every file under a directory tree. The catch is the path argument people forget, the -r vs -R symlink difference, and the unfiltered crawl into node_modules and .git. The flag reference, the --include and --exclude-dir filters, the macOS BSD vs GNU gaps, and when to reach for ripgrep or git grep instead.

Use grep -l 'pattern' files to list only the filenames that contain a match. The inverted grep -L, the recursive grep -rl one-liner, the NUL-safe xargs pipeline for find-and-replace, and the macOS BSD vs GNU notes.

How to List Only Filenames with grep -l

grep -l prints the name of each file that contains a match and stops reading at the first hit, which makes it the fast answer to 'which files contain this string'. The lowercase -l, the inverted -L for files missing a pattern, the grep -rl one-liner, the NUL-safe xargs pipeline for find-and-replace, and the BSD vs GNU notes.