Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

grep: search in text

By updated

Improve this page

When you need to find an error line in a log of several thousand lines, look for a keyword in source code or filter the output of a command, grep is the essential tool. Simple and powerful, grep searches for text patterns (plain or regex) and prints the matching lines, straight from the command line.

What you will learn

  • Search for a simple pattern in a file
  • Use the essential options -i, -v, -n, -c
  • Show the context with -A, -B, -C
  • Use basic and extended (-E) regular expressions
  • Run recursive searches with -r

The grep command in the Linux ecosystem

grep belongs to the text-processing commands of Linux. Each tool has its speciality, and grep is the one that selects lines:

CommandSpecialityWhen to use it
grepFilter lines by patternSearch, filter, diagnose
awkAdvanced column processingCalculations, conditions, complex formats
sedEdit text as a streamReplace, delete, insert
cutExtract columnsDelimited files (CSV, TSV)
findSearch for filesFind by name, size, date
xargsTurn stdin into argumentsChain commands over lists

When to use grep vs awk?

The table below shows where grep is enough and where awk takes over, as soon as you need columns or calculations:

Situationgrepawk
Search for a word in a fileYes, grep "mot" fichierYes, but overkill
Ignore caseYes, grep -i "mot"Yes, tolower()
Show the lines containing a patternYes, perfectYes
Extract a specific columnNoYes, awk '{print $2}'
Count exact occurrencesNo, -c counts linesYes
Calculations on the matched linesNoYes
Complex conditions (AND, OR, IF)Watch out: limitedYes

Frequent combinations:

Terminal window
# grep + cut: filter then extract a column
grep 'ERROR' app.log | cut -d' ' -f4
# grep + sort + uniq: count the types of errors
grep 'ERROR' app.log | cut -d':' -f2 | sort | uniq -c
# grep + xargs: act on the files containing a pattern
grep -rl 'TODO' src/ | xargs -I{} echo "Fichier à traiter: {}"
# grep + wc: count the matching lines
grep -c 'ERROR' *.log | grep -v ':0$'

Understand the grep command in 2 minutes

The mental model below sums up how grep works: an input, a pattern, the matching lines as output.

🧠 Mental model: How grep works

grep = Set the input → Give the pattern → Get the matching lines

grep reads its input line by line and tests the pattern on each one: a matching line goes to standard output, the others are discarded; -i, -v, -r, -n and -c adjust the search.matchesno matchgrepINPUTfile · stdin, line by linePATTERN'error' · '^start'OUTPUTmatching lines on stdoutline discarded-i case · -v invert · -r recursive-n line number · -c count
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Key points

  • grep filters the lines containing a pattern (text or regex)
  • Without a file, grep reads from stdin (pipe)
  • By default, the search is case-sensitive
  • -i to ignore case, -v to invert (lines without the pattern)
  • -n prints line numbers, -c counts the matching lines
  • grep -E enables extended regex (|, +, ?, etc.); egrep is obsolescent

Golden rules

1
Always protect the pattern with quotes Stops the shell from interpreting special characters (*, ?, |, etc.)
2
Use -E for regex with alternation or quantifiers Basic grep requires escaping | + ? (), -E enables them directly

Essential vocabulary

-i
Ignore case (case-insensitive)
-v
Invert: lines WITHOUT the pattern
-n
Print line numbers
-c
Count lines (not occurrences)
-r
Recursive search in directories
-l
Print file names only
📚 Going further: 6 advanced options
-E
Extended regex (replaces egrep)
-w
Whole word only
-o
Print only the match
-A N
N lines after each match
-B N
N lines before each match
-C N
N lines of context (before and after)

grep reads the file or stdin line by line, tests whether the pattern is present, then prints the matching lines on stdout.

Minimal syntax

The order of the three elements is not negotiable: the options come before the pattern, and the pattern before the files. If you swap pattern and file, grep treats your pattern as a file name, and you get a confusing No such file or directory error. When [FICHIER...] is missing, grep reads its standard input, which is exactly the behaviour you want in a pipeline.

Terminal window
grep [OPTIONS] MOTIF [FICHIER...]

Search modes

grep offers several modes depending on the type of pattern. The choice comes down to a single question: does your pattern contain characters that have a special meaning in regex? If so and you want them literal, -F is the safest answer. If on the contrary you want to use alternation or quantifiers, -E spares you from escaping half the pattern.

ModeCommandUseExample
Plain textgrepLiteral patternsgrep "ERROR" log.txt
Basic regex (BRE)grepBasic metacharactersgrep "^[0-9]" file
Extended regex (ERE)grep -E|, +, ?, () without escapinggrep -E "a|b"
Literal textgrep -FDisables regexgrep -F "*.txt" file

Essential options

These eight options cover almost every daily use. They combine freely: grep -rin "timeout" /var/log/ stacks recursion, ignored case and line numbers. Two of them change the nature of the output rather than the selection: -c returns a number, -l returns paths. They are the ones you chain in a script, because their output is easily consumed by another command.

OptionEffectExample
-iIgnore casegrep -i "error"
-vInvert (lines without the pattern)grep -v "DEBUG"
-nPrint line numbersgrep -n "ERROR"
-cCount linesgrep -c "ERROR"
-lPrint only file namesgrep -l "TODO"
-rRecursive searchgrep -r "TODO" src/
-wWhole word onlygrep -w "port"
-oPrint only the matchgrep -o "[0-9]*"

Show the context

An isolated error line rarely says what happened. These three options bring back the neighbouring lines and turn a raw message into a readable sequence. One detail to know: when two matches are close, grep inserts a -- line between the context blocks to mark the gap. This line pollutes downstream processing, remove it with --no-group-separator if you chain the output.

OptionEffectExample
-A NN lines after (After)grep -A 3 "ERROR"
-B NN lines before (Before)grep -B 2 "ERROR"
-C NN lines before and after (Context)grep -C 2 "ERROR"
Terminal window
# See what happened before and after an error
grep -C 3 "Exception" application.log
# Show the context after each error (stack trace)
grep -A 10 "ERROR" app.log

Basic regular expressions

These patterns belong to the BRE (Basic Regular Expression) dialect, the one grep uses without any option. Two behaviours surprise there: * does not mean "anything" as in the shell, but "zero or more times the previous character"; and word boundaries are written \< and \>, with backslashes: a GNU extension, absent from the POSIX standard. Remember the two anchors ^ and $ first, they are the ones that remove the most false positives.

PatternMeaningExample
^motifStart of linegrep "^ERROR"
motif$End of linegrep "done$"
.Any single charactergrep "err.r"
*Zero or more of the previous onegrep "ab*c"
[abc]One of a, b or cgrep "[aeiou]"
[^abc]None of a, b or cgrep "[^0-9]"
\<mot\>Whole word (BRE)grep "\<port\>"

Extended regular expressions (-E)

With -E, you switch to ERE (Extended Regular Expression) and the advanced metacharacters become available without escaping:

PatternMeaningExample
a|ba OR bgrep -E "ERROR|WARNING"
+One or more of the previous onegrep -E "[0-9]+"
?Zero or one of the previous onegrep -E "colou?r"
(abc)Groupinggrep -E "(ab)+"
{n,m}Between n and m repetitionsgrep -E "[0-9]{2,4}"
Terminal window
# Find ERROR or WARNING or CRITICAL
grep -E "ERROR|WARNING|CRITICAL" app.log
# Find the lines with a 2 to 4 digit number
grep -E "[0-9]{2,4}" data.txt
# Extract the IP addresses
grep -oE "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log

Recursive search in a project

Recursion is the mode where grep can become slow and noisy. Two options settle almost everything: --exclude-dir to skip dependency directories and the Git history, --include to target only one file type. Without them, a search at the root of a Node project walks through node_modules and drowns the useful result. Note also that -r follows the symbolic links given as arguments but not those met along the way; use -R if you want to follow them all.

Terminal window
# Every file of the current directory
grep -r "TODO" .
# With line numbers
grep -rn "FIXME" src/
# File names only
grep -rl "password" /etc/
# Exclude some directories
grep -r "TODO" --exclude-dir={node_modules,.git} .
# Filter by extension
grep -r "import" --include="*.py" .

Typical errors (and fixes)

All these situations share the same signature: grep does not crash, it simply returns an unexpected result. That is what makes them costly, since nothing reports the error. The reflex that avoids almost all of them is to test the pattern on a single known line before running it on a whole file.

ErrorCauseFix
No resultDifferent caseUse -i
Partial resultsSubstring matchUse -w for a whole word
Regex not recognisedBasic grepUse -E for extended regex
binary file matchesBinary file detectedUse -a to force text
grep finds itselfps | grepAdd | grep -v grep

Common search patterns

Now that you understand the logic of grep, here are sixteen ready-to-use patterns. Each one gives its general formula and a concrete example.

Find the lines containing a word. The output shows the whole line, not only the matching part; the return code is 0 if at least one line matches, 1 otherwise, which makes this form usable as is in a script test.

Terminal window
grep "MOTIF" <fichier> # formula
grep "ERROR" application.log # example
  • MOTIF: text or regular expression to search for

Ignore case

Search without telling upper and lower case apart. It is the first option to reach for when an empty search surprises you although you are sure of the content: log levels are written ERROR by some, Error by others.

Terminal window
grep -i "MOTIF" <fichier> # formula
grep -i "error" logs.txt # example
  • -i: case-insensitive search

Exclude a pattern

Print the lines that do not contain the pattern, thanks to -v, the inverted selection.

Terminal window
grep -v "MOTIF" <fichier> # formula
grep -v "DEBUG" application.log # example
  • -v: inverts the selection

Count the lines

Count the number of matching lines. Watch the exact meaning: -c counts lines, not occurrences. A line containing the pattern three times only counts as one.

Terminal window
grep -c "MOTIF" <fichier> # formula
grep -c "ERROR" application.log # example
  • -c: returns only the count, not the lines

With line numbers

See on which lines the matches sit, with -n.

Terminal window
grep -n "MOTIF" <fichier> # formula
grep -n "ERROR" application.log # example
  • -n: prefixes each line with its number

Context after the match

See N lines after each match. It is the form suited to exception traces, where the error message fits on one line but the call stack unfolds on the following ones.

Terminal window
grep -A N "MOTIF" <fichier> # formula
grep -A 3 "Exception" error.log # example
  • -A N: N lines after (After)

Context before the match

See N lines before each match. Useful in the opposite case: understand what the service was trying to do just before the failure, information that is always missing from the error line itself.

Terminal window
grep -B N "MOTIF" <fichier> # formula
grep -B 2 "FAILURE" job.log # example
  • -B N: N lines before (Before)

Context before and after

See N lines on both sides of each match, with -C, the context option.

Terminal window
grep -C N "MOTIF" <fichier> # formula
grep -C 2 "timeout" network.log # example
  • -C N: N lines of context (Context)

Whole word only

Avoid partial matches. Without this option, looking for port in a configuration also brings back import, export and support, which drowns the useful result under unrelated lines.

Terminal window
grep -w "MOTIF" <fichier> # formula
grep -w "port" config.conf # example
  • -w: enforces a word boundary

Search in every file of a directory. On a real project, always add --exclude-dir: without it, grep goes down into .git and into the dependency directories, and the search becomes both slow and unreadable.

Terminal window
grep -r "MOTIF" <dossier> # formula
grep -r "TODO" ./src/ # example
  • -r: goes down into subdirectories

File names only

List the files containing the pattern, without printing the lines, with -l.

Terminal window
grep -l "MOTIF" <fichiers> # formula
grep -rl "password" /etc/ # example
  • -l: prints only the file paths

Extended regex (alternation)

Search for several patterns with an OR. Without -E, the vertical bar is taken literally and grep looks for the literal string ERROR|WARNING, which exists nowhere: you get zero results without the slightest error message.

Terminal window
grep -E "MOTIF1|MOTIF2" <fichier> # formula
grep -E "ERROR|WARNING|CRITICAL" app.log # example
  • -E: enables extended regex
  • |: alternation (OR)

Extract only the matching part, not the whole line, with -o: the basis of any extraction.

Terminal window
grep -o "PATTERN" <fichier> # formula
grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log # example
  • -o: prints only the match

Exclude comments

Ignore the comment lines and the empty lines of a configuration file.

Terminal window
grep -v "^#" <fichier> | grep -v "^$" # formula
grep -v "^#" config.conf | grep -v "^$" # example
  • ^#: lines starting with #
  • ^$: empty lines

Filtering pipeline

Filter the output of another command. It is the most common use in administration. Remember that grep only sees the standard output here: error messages slip past the filter until you add 2>&1.

Terminal window
<commande> | grep "MOTIF" # formula
ps aux | grep nginx # example
  • grep filters what arrives on its standard input

Search in several files at once. As soon as there is more than one file, grep prefixes each line with the file name. This prefix is what lets you sort or count per file afterwards, but it breaks processing that expected the raw line.

Terminal window
grep "MOTIF" <fichier1> <fichier2> ... # formula
grep "server" *.conf # example
  • *: shell wildcard to target several files

Pitfalls to avoid

Eight errors come back constantly with grep. For each one: the faulty command, the symptom, the cause and the correction.

Unquoted pattern

An unquoted pattern is read by the shell before grep, which changes its meaning.

Terminal window
grep *.log fichier.txt # WRONG: the shell interprets the * before grep

Symptom: error or unexpected results. Cause: the shell interprets the special characters (*, ?, |) before grep sees them.

Terminal window
grep -F "*.log" fichier.txt # RIGHT: protected from the shell, and read literally

Quotes are enough to keep the pattern away from the shell, but not to make it literal: in "*.log", the . is still a metacharacter that accepts any character. To search for exactly the string *.log, add -F.

Forgetting -i for case

By default, grep respects case, and log levels vary from one application to another.

Terminal window
grep "error" fichier.log # WRONG: does not find "ERROR" or "Error"

Symptom: lines are missing from the results. Cause: by default, grep is case-sensitive.

Terminal window
grep -i "error" fichier.log # RIGHT: case ignored

grep finds itself

The grep process shows up in the ps list it is filtering.

Terminal window
ps aux | grep nginx # WRONG: also prints the line of the "grep nginx" process

Symptom: a stray line containing grep in the results. Cause: the grep process itself appears in the ps list.

Terminal window
ps aux | grep nginx | grep -v grep # RIGHT: the grep line is excluded

Basic versus extended regex

The vertical bar only means OR in extended regex.

Terminal window
grep "ERROR|WARNING" fichier.log # WRONG: looks for the literal text "ERROR|WARNING"

Symptom: no result. Cause: in basic regex, | is not alternation, you must escape it or use -E.

Terminal window
grep -E "ERROR|WARNING" fichier.log # RIGHT: extended regex

Unescaped special characters

A metacharacter in a pattern you meant as literal text changes the search.

Terminal window
grep "prix: 10$" fichier.txt # WRONG: the $ is read as "end of line"

Symptom: unexpected results. Cause: $ is a regex metacharacter.

Terminal window
grep -F "prix: 10$" fichier.txt # RIGHT: -F treats the pattern as literal text

-c counts lines, not occurrences

-c answers "how many lines", not "how many occurrences".

Terminal window
grep -c "mot" fichier.txt # WRONG: returns 2 even if "mot" appears 5 times on 2 lines

Symptom: a count lower than the real number of occurrences. Cause: -c counts the lines containing the pattern, not the total number of matches.

Terminal window
grep -o "mot" fichier.txt | wc -l # RIGHT: counts the real occurrences

Binary files skipped

A file with a null byte is treated as binary, and grep hides its lines.

Terminal window
grep "motif" fichier.bin # WRONG: prints "grep: fichier.bin: binary file matches" instead of the content

Symptom: a message instead of the result. Cause: grep detects a binary file (a single null byte is enough) and does not print its content. Since grep 3.5, this message goes to standard error: a 2>/dev/null redirection makes it disappear, and the command then seems to find nothing.

Terminal window
grep -a "motif" fichier.bin # RIGHT: -a forces text processing

Unwanted partial match

grep searches for a substring, not a word.

Terminal window
grep "port" config.conf # WRONG: also finds "import", "export", "support"

Symptom: too many irrelevant results. Cause: grep looks for the substring, not the whole word.

Terminal window
grep -w "port" config.conf # RIGHT: -w enforces the whole word

Hands-on lab

Nothing replaces practice. Create the test environment below, then go through the twelve steps. At the end, clean up with rm -rf ~/grep-lab.

Prepare the ground

This script creates four test files in ~/grep-lab: an application log, a configuration file, Python source code and a contact list.

Terminal window
# Create the lab
mkdir -p ~/grep-lab
cd ~/grep-lab
# Application log file
cat > application.log << 'EOF'
2025-01-15 08:00:01 INFO Application démarrée
2025-01-15 08:00:02 DEBUG Connexion base de données établie
2025-01-15 08:01:15 INFO Utilisateur alice connecté
2025-01-15 08:02:30 WARNING Mémoire utilisée: 75%
2025-01-15 08:03:45 ERROR Timeout lors de la requête API
2025-01-15 08:03:46 DEBUG Retry 1/3 en cours...
2025-01-15 08:03:48 ERROR Timeout lors de la requête API
2025-01-15 08:03:49 DEBUG Retry 2/3 en cours...
2025-01-15 08:03:51 INFO Requête réussie après retry
2025-01-15 08:05:00 INFO Utilisateur bob connecté
2025-01-15 08:10:00 WARNING Disque utilisé: 85%
2025-01-15 08:15:00 ERROR Permission denied: /var/data/secret.txt
2025-01-15 08:20:00 INFO Batch job terminé avec succès
EOF
# Configuration file
cat > config.conf << 'EOF'
# Configuration serveur
server.host=localhost
server.port=8080
server.ssl.enabled=true
# Assistance
support.email=support@example.com
# Base de données
database.host=db.example.com
database.port=5432
database.name=production
database.user=admin
# Cache
cache.enabled=true
cache.ttl=3600
# Logging
log.level=INFO
log.file=/var/log/app.log
EOF
# Python source code
cat > app.py << 'EOF'
#!/usr/bin/env python3
"""Application principale"""
import os
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def connect_database(host, port):
logger.info(f"Connexion à {host}:{port}")
# TODO: implémenter la connexion réelle
return True
def process_data(data):
# FIXME: gérer les erreurs
if not data:
raise ValueError("Data is empty")
return data.upper()
if __name__ == "__main__":
connect_database("localhost", 5432)
# TODO: ajouter la logique métier
EOF
# File with email addresses
cat > contacts.txt << 'EOF'
alice@example.com - Alice Martin
bob.smith@company.org - Bob Smith
charlie@test.co.uk - Charlie Brown
invalid-email - Non valide
ADMIN@EXAMPLE.COM - Administrateur
support@help-desk.net - Support technique
EOF
echo "Correct : Lab créé dans ~/grep-lab"
ls -la ~/grep-lab

The twelve steps

Go through them in order: each step reuses a notion from the previous one, and the last four combine several options. After each command, compare what you get with the explanation line: if the number of lines printed differs, it is almost always the pattern that is at fault, not the command. The lab files are deliberately short so that you can check the result by eye.

  1. Simple search. Find every line containing ERROR in application.log.

    Terminal window
    grep "ERROR" ~/grep-lab/application.log

    grep prints every line containing the pattern, here three lines.

  2. Ignore case. Find every variant of error, whatever the case.

    Terminal window
    grep -i "error" ~/grep-lab/application.log

    -i makes the search case-insensitive.

  3. Line numbers. Print the errors with their line number.

    Terminal window
    grep -n "ERROR" ~/grep-lab/application.log

    -n prefixes each result with its line number in the file.

  4. Invert the search. Print every line except those containing DEBUG.

    Terminal window
    grep -v "DEBUG" ~/grep-lab/application.log

    -v inverts the filter: it keeps the lines that do not match.

  5. Count the occurrences. Count the number of lines containing INFO.

    Terminal window
    grep -c "INFO" ~/grep-lab/application.log

    -c returns the number of matching lines, not the lines themselves.

  6. Context after. Print each ERROR followed by the next 2 lines.

    Terminal window
    grep -A 2 "ERROR" ~/grep-lab/application.log

    -A N adds N lines of context after each match. For context before and after: grep -C 1 "ERROR" ~/grep-lab/application.log.

  7. Whole word. Find INFO without catching a possible INFORMATION.

    Terminal window
    grep -w "INFO" ~/grep-lab/application.log

    -w enforces a word boundary around the pattern.

  8. Basic regular expressions. Find the lines starting with a date (2025-).

    Terminal window
    grep "^2025-" ~/grep-lab/application.log

    ^ stands for the start of line in regex.

  9. Extended regex with alternation. Find the lines containing ERROR or WARNING.

    Terminal window
    grep -E "ERROR|WARNING" ~/grep-lab/application.log

    -E enables extended regex, where | means "or".

  10. Exclude comments. Print the lines of config.conf that are not comments.

    Terminal window
    grep -v "^#" ~/grep-lab/config.conf

    ^# targets the lines starting with #; -v excludes them.

  11. Pipeline with grep. List the running Python processes.

    Terminal window
    ps aux | grep python | grep -v grep

    grep filters the output of ps; the second grep -v grep removes the line of the grep itself.

  12. Recursive search. Find every TODO in the lab files.

    Terminal window
    grep -r "TODO" ~/grep-lab/

    -r walks recursively through every file of the directory, the TODOs are in app.py.

Progressive exercises

Ten exercises to validate your mastery. All of them work on the files of the ~/grep-lab lab. Search on your own first, then unfold the solution to check yourself.

Foundation level

These four exercises only use a single option at a time. Do them without looking at the tables above: the goal is to memorise the letter tied to each effect, -i, -v and -c, because they are the ones you will type most often in production.

Exercise 1, find the errors. Print every line containing ERROR in application.log. Hint: simple syntax grep "motif" fichier.

Show the solution
Terminal window
grep "ERROR" ~/grep-lab/application.log

grep prints the lines containing the given pattern.

Exercise 2, ignore case. Find every variant of warning (warning, WARNING, Warning…). Hint: -i for case-insensitive.

Show the solution
Terminal window
grep -i "warning" ~/grep-lab/application.log

-i makes the search case-insensitive.

Exercise 3, lines without DEBUG. Print every line except those containing DEBUG. Hint: -v inverts the selection.

Show the solution
Terminal window
grep -v "DEBUG" ~/grep-lab/application.log

-v prints the lines that do not match the pattern.

Exercise 4, count the errors. Count the number of lines containing ERROR. Hint: -c for count.

Show the solution
Terminal window
grep -c "ERROR" ~/grep-lab/application.log

-c returns only the number of matching lines.

Composition level

One step up: these five exercises ask you to pick the right option among several plausible candidates. Exercise 6 is the most instructive, since the naive version without -w returns a result, but the wrong one. A non-empty result is never proof that the pattern was correct.

Exercise 5, error context. Print each ERROR line with the next 2 lines. Hint: -A for after.

Show the solution
Terminal window
grep -A 2 "ERROR" ~/grep-lab/application.log

-A N prints N lines after each match.

Exercise 6, whole word. Find port in config.conf without catching import or export. Hint: -w for word.

Show the solution
Terminal window
grep -w "port" ~/grep-lab/config.conf

-w guarantees that the pattern is a whole word, not a substring: two lines come out (server.port and database.port), against three without the option, support.email containing port inside support.

Exercise 7, regex with alternation. Find the lines containing ERROR or WARNING. Hint: -E for extended regex, | for OR.

Show the solution
Terminal window
grep -E "ERROR|WARNING" ~/grep-lab/application.log

-E enables extended regex, where | means "or".

Exercise 8, exclude comments. Print the lines of config.conf without the comments (#). Hint: ^# targets the start of line, -v inverts.

Show the solution
Terminal window
grep -v "^#" ~/grep-lab/config.conf

^# targets the lines starting with #; -v excludes them.

Exercise 9, recursive search. Find every TODO in the lab files. Hint: -r for recursive.

Show the solution
Terminal window
grep -r "TODO" ~/grep-lab/

-r walks recursively through every subdirectory.

Industrialisation level

Last exercise, and the only one that does not work on a file but on the output of another command. It is the most frequent use of grep in administration, and the one where the self-match trap fires every time: the grep process you have just launched itself appears in the list returned by ps.

Exercise 10, filter a process. List the processes containing ssh without printing the line of the grep itself. Hint: a second grep -v grep.

Show the solution
Terminal window
ps aux | grep ssh | grep -v grep

The second grep -v grep excludes the line of the grep process from the output.

Troubleshooting

When grep does not return what you expect, the problem almost always comes from the pattern (case, regex, escaping). Here is how to diagnose it, then how to read the error messages.

Diagnostic methods

The approach is to shrink the problem: instead of wondering why the pattern fails on a ten-thousand-line file, test it on a test line that you build with echo. If the pattern works there and not on the file, the pattern is fine and it is the content that differs from what you imagine: non-breaking spaces, Windows carriage returns, invisible characters. That is what cat -A reveals, by printing ^M$ at the end of the line for a DOS-format file.

Terminal window
# Test the pattern on a simple line
echo "test ERROR test" | grep "ERROR"
# Show the non-printable characters of the file
cat -A fichier.log | head
# Validate a regular expression on its own
echo "test123" | grep -E "[0-9]+"
# See how many lines match before filtering
grep -c "MOTIF" fichier.log

Frequent errors

Unlike the previous pitfalls, these cases produce an explicit message on standard error. That is good news: the cause is identified by grep itself. Always read the message in full, it contains the path or the faulty part of the pattern, which saves you from looking in the wrong place.

Error messageProbable causeFix
grep: <fichier>: No such file or directoryMissing file or wrong pathCheck with ls, review the relative/absolute path
grep: Unmatched [, [^, [:, [., or [=Opening bracket not closed (grep "[abc")Close the bracket, or -F for a literal search
grep: Unmatched ( or \(Unclosed parenthesis in -EClose the parenthesis, or escape it: \(
grep: Invalid content of \{\}Inconsistent interval, such as {2,1}Write {min,max} with min lower than max
grep: <fichier>: binary file matchesThe file is detected as binaryForce text mode with grep -a

grep cheat sheet

The two tables below condense the whole guide on one screen. The first gathers the options of the command, the second the metacharacters of the pattern. The distinction matters: an option goes before the pattern and changes the behaviour of grep, a metacharacter lives inside the pattern and must be protected by quotes so that the shell leaves it alone.

Syntax and options

Take this table as a starting list, not as a complete reference. The first four rows cover most of daily work; the context options -A, -B and -C become essential as soon as you read error traces, where the message line is never enough to understand the sequence.

ElementRoleExample
grep "motif" fichierSimple searchgrep "ERROR" log.txt
-iIgnore casegrep -i "error" log.txt
-vInvert (exclude)grep -v "DEBUG" log.txt
-nLine numbersgrep -n "ERROR" log.txt
-cCount linesgrep -c "ERROR" log.txt
-wWhole wordgrep -w "port" config
-lFile names onlygrep -rl "TODO" .
-rRecursive searchgrep -r "TODO" src/
-oPrint only the matchgrep -o "IP: [0-9.]*"
-A NN lines aftergrep -A 3 "ERROR"
-B NN lines beforegrep -B 2 "ERROR"
-C NN lines of contextgrep -C 2 "ERROR"
-EExtended regexgrep -E "a|b"
-FLiteral text (no regex)grep -F "*.txt"

Regular expressions

Only one row of this table depends on an option: the alternation a|b only makes sense with -E. All the others work in BRE, hence without an option. If you had to remember only two entries, take the anchors ^ and $: they are the ones that turn an approximate search into a precise filter, for example to keep only the comment lines of a configuration file.

PatternRoleExample
^motifStart of linegrep "^#" config
motif$End of linegrep "done$" log
.Any single charactergrep "err.r"
*Zero or more occurrences of the previous onegrep "ab*c"
[abc]One character from the listgrep "[aeiou]"
[^abc]Anything but these charactersgrep "[^0-9]"
a|ba OR b (with -E)grep -E "cat|dog"
\<mot\>Whole word (basic regex)grep "\<port\>"

Mastery checklist

You master grep when you can tick each of these points, from the simple search to the pipeline and extended regex:

  • I can run a simple search with grep
  • I can ignore case with -i
  • I can invert the search with -v
  • I can count lines with -c
  • I can show the context with -A, -B, -C
  • I can search for a whole word with -w
  • I can use extended regex with -E
  • I can run a recursive search with -r
  • I can fit grep into a pipeline

Conclusion

The grep command is the most used text-search tool on Linux. Its simple syntax (grep "motif" fichier) covers 80% of daily needs. The options -i (case), -v (invert), -n (numbers), -r (recursive) and -E (extended regex) cover the advanced cases. For more complex processing (column extraction, calculations), combine grep with awk or cut.

Key points

  • grep prints the whole lines that contain the pattern; its return code is 0 if at least one matches, 1 otherwise.
  • The pattern always goes between quotes, otherwise the shell interprets *, ? and | before grep.
  • -i ignores case, -v inverts, -n numbers, -w enforces the whole word.
  • -c counts lines, not occurrences: grep -o motif | wc -l counts occurrences.
  • Without an option, grep speaks BRE: alternation | requires -E, and -F disables regex entirely.
  • -A, -B and -C bring back the context of an error, essential to read a trace.
  • grep -r combines with --exclude-dir and --include to stay fast on a real project.

Knowledge check

Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn