Negative matching using grep (match lines that do not contain foo)
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
To match lines that do not contain a pattern using grep, use the -v (or --invert-match) flag. This inverts the match logic so that grep prints only the lines that fail to match the given pattern. It works with fixed strings, basic regular expressions, and extended regular expressions, and it composes cleanly with every other grep option.
That single flag is the answer, but the rest of this article covers the practical situations where negative matching gets more nuanced: excluding multiple patterns, combining with case-insensitive search, filtering structured data, and handling edge cases in scripts and pipelines.
How -v Works
The -v flag inverts the exit behavior and output of grep. Without -v, grep prints lines that match. With -v, it prints lines that do not match.
Consider a file servers.txt:
To show only lines that do not contain "stopped":
Output:
The matched pattern is excluded. Everything else passes through.
Excluding Multiple Patterns
When you need to exclude more than one pattern, you have several options.
Using Multiple -e Flags
Output:
Each -e adds a pattern to the match set, and -v inverts the entire combined result. A line is excluded if it matches any of the listed patterns.
Piping Multiple grep -v Commands
This produces the same result. Chaining is sometimes clearer when filters are built dynamically in a script, but it spawns multiple processes.
Using Extended Regex Alternation
The -E flag enables extended regular expressions, and the pipe character acts as logical OR. This is the most compact form for excluding multiple fixed strings.
Combining With Other grep Options
The -v flag composes with every other grep option. Here are the most useful combinations.
Case-Insensitive Negative Match
This excludes lines containing "error", "Error", "ERROR", or any other case variation.
Counting Excluded Lines
The -c flag prints the count of matching lines. Combined with -v, it counts lines that do not contain the pattern.
Showing Line Numbers
The -n flag prepends line numbers, which is useful when you need to locate the non-matching lines in context.
Whole-Word Matching
Without -w, the pattern "to" would also match inside words like "tomato" or "together". The -w flag requires the pattern to appear as a whole word, so -vw "to" excludes only lines containing the standalone word "to".
Negative Matching With Regular Expressions
The real power of negative matching shows up when combined with regex patterns.
Exclude Lines Starting With a Comment
This filters out comment lines in configuration files (lines beginning with #).
Exclude Blank Lines
Combined with comment removal:
Or in a single expression:
This is a common pattern for extracting only the meaningful content from configuration files.
Exclude Lines Matching a Complex Pattern
This excludes timestamped DEBUG log lines while keeping everything else. The regex matches a date-like prefix followed by the word "DEBUG" anywhere on the line.
Practical Use Cases
Filtering Log Files
Cleaning Process Lists
This is a classic pattern. When you grep for a process name, the grep command itself shows up in the ps output. The trailing grep -v "grep" removes that false positive.
Filtering CSV or TSV Data
Validating File Contents in CI
The inverse check (ensuring something is absent) is what negative matching enables in automated validation.
Reference Table
| Option | Description | Example |
-v / --invert-match | Print lines that do not match | grep -v "foo" file.txt |
-i | Case-insensitive matching | grep -vi "foo" file.txt |
-E | Extended regex (alternation with pipe) | grep -vE "foo|bar" file.txt |
-w | Match whole words only | grep -vw "to" file.txt |
-c | Count non-matching lines (with -v) | grep -vc "foo" file.txt |
-n | Show line numbers | grep -vn "foo" file.txt |
-r | Recursive search through directories | grep -rv "TODO" src/ |
-l | List files with no matches (with -vl or -L) | grep -L "pattern" *.txt |
Note: grep -L (uppercase L) is equivalent to grep --files-without-match and directly lists files that do not contain the pattern, without needing -v.
Common Pitfalls
Forgetting that -v inverts the entire combined pattern set is a frequent source of confusion. When using grep -v -e "foo" -e "bar", a line is excluded if it matches either pattern, not only if it matches both. If you need to exclude lines that contain both "foo" and "bar" simultaneously, you need a different approach (a regex like grep -v "foo.*bar\|bar.*foo" or a pipeline with awk).
Using -v without anchoring patterns can produce unexpected results. For instance, grep -v "log" will also exclude lines containing "catalog", "dialog", or "login". Use -w for whole-word matching or anchor with \b word boundaries.
Neglecting the grep self-match problem in process listings (ps aux | grep pattern) is a classic mistake. Always add | grep -v grep or use pgrep instead.
Assuming -v affects the exit code the same way as normal matching can break scripts. With -v, grep returns exit code 0 if any lines did not match (were printed) and 1 if all lines matched (nothing printed). Script conditions need to account for this inversion.
Not quoting patterns that contain shell metacharacters (like |, *, ?) leads to the shell expanding them before grep sees them. Always quote your patterns.
Summary
- Use
grep -v "pattern"to print lines that do not contain a pattern. - Exclude multiple patterns with
-eflags, piped commands, or extended regex alternation (-vE "a|b"). - Combine
-vwith-i(case-insensitive),-w(whole word),-n(line numbers), or-c(count) as needed. - Use
grep -Lto list files that do not contain a pattern at all. - Watch out for unanchored patterns matching inside longer words, and always quote patterns to prevent shell expansion.

