Linux command: grep
Sooner or later you need to find something in a haystack of text. A single error hides in a log of a million lines. A setting lives somewhere in a folder of hundreds of config files. A word appears across a whole source tree and you have no idea where. For all of these, one small command does the work in a fraction of a second: grep.
1. The Basics
The job of grep is to read text line by line and print every line that contains a pattern you give it. That is the whole idea. You hand it a pattern and some text, and it shows you the matching lines and hides the rest.
The basic shape of the command never changes: the word grep, a pattern, and where to look.
$ grep "error" /var/log/syslog
Jul 7 14:03:11 web1 kernel: [12] EXT4-fs error on device sda1
The file may hold a million lines; grep prints only the ones that contain error. Everything else stays out of your way.
Two things make grep special. First, it is safe: it only reads, it never changes a file. You can point it at a production log or a system config and you cannot damage anything. Second, it is fast on any size of input. grep works one line at a time and never loads the whole file into memory, so it searches a ten-gigabyte log on a small server without trouble.
grep answers one clear question, "which lines contain this?", and it answers it faster than any file manager or text editor ever will.
Back to topThe right mental model:
grepis a filter. Text flows in, and only the lines that match your pattern flow out. Everything else about it, the dozens of options and the regular expressions, is just a way to describe what "match" means.
2. Where the Name Comes From
The name looks like random letters, but it is an acronym with a precise meaning. It comes from a command in the old ed line editor:
g/re/p = Global / Regular Expression / Print
In ed, you could type g/re/p to say: globally, across the whole file, find every line matching this regular expression, and print it. That single editor command was so useful that it grew into a program of its own, and the program kept the name.
So grep is not a made-up word. Every time you type it, you are spelling out exactly what it does: search everywhere for a pattern and print what you find.
Back to topUnderstanding the name gives you the mental model for free: global (all lines), regular expression (a pattern, not just a word), print (show the matches). That is the entire command in three letters.
3. A Short History
The story of grep is one of the famous anecdotes of early Unix. In the early 1970s at Bell Labs, Ken Thompson's ed editor already had the g/re/p command. A colleague, Doug McIlroy, needed to search text for patterns as part of his work and asked Thompson for help.
Thompson took the regular-expression code out of ed, wrapped it into a standalone program overnight, and delivered grep the next morning. It first shipped in Version 4 Unix around 1973. A tool built as a quick favour became one of the most used commands in computing.
As Unix grew, so did the grep family. Two relatives arrived in Version 7 Unix in 1979: egrep for a richer "extended" pattern syntax, and fgrep for fast searches of plain fixed strings. Later, the GNU project rewrote grep from scratch, and GNU grep became famous for its speed, using clever algorithms to skip over text instead of checking every single byte.
| Era | Milestone |
|---|---|
| ~1973 | Ken Thompson writes grep for Version 4 Unix, from ed's g/re/p |
| 1979 | egrep (extended) and fgrep (fixed strings) appear in Version 7 Unix |
| ~1988 | GNU grep arrives, rewritten for speed by Mike Haertel |
| 1990s | POSIX folds the variants into one command: grep -E and grep -F |
| Today | GNU grep is the default on Linux; egrep/fgrep are deprecated |
Because POSIX folded the extra tools into options, modern GNU grep now prints a warning if you run egrep or fgrep directly. The recommended forms are grep -E and grep -F, which do exactly the same thing.
4. Simple Use Cases
4.1 The Simplest Possible Use
Give grep a pattern and a file. It prints every line that contains the pattern.
$ grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
Wrap the pattern in double quotes as a habit. It is not always required, but it protects patterns that contain spaces or special characters from the shell, which would otherwise try to interpret them before grep ever sees them.
4.2 Ignore Upper and Lower Case
By default grep is case-sensitive, so Error and error are different. The -i flag (short for ignore-case) makes the search match either:
$ grep -i "warning" application.log
WARNING: disk almost full
Warning: retrying connection
4.3 Show Line Numbers
The -n flag (short for line-number) puts the line number in front of every match. This is what you want when searching source code, because you can then jump straight to that line in an editor:
$ grep -n "TODO" main.c
42: // TODO: handle the empty case
87: // TODO: free this buffer
4.4 Count the Matches
Sometimes you want the number, not the lines. The -c flag (short for count) prints how many lines matched:
$ grep -c "404" access.log
128
This says 128 lines contain 404, without printing all 128 of them. Note that it counts matching lines, not total matches, so two hits on one line still count as one.
4.5 Invert the Match
The -v flag (short for invert-match) turns the question around: it prints the lines that do not match. A classic use is reading a config file without its comments and blank lines:
$ grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"
Port 22
PermitRootLogin no
PasswordAuthentication no
The first grep drops lines that start with #, the second drops empty lines, and you are left with only the settings that are actually active.
5. Moderate Use Cases
5.1 Searching a Whole Directory Tree
The option that changes everything is -r (short for recursive). It tells grep to walk down through a folder and every folder inside it, searching every file. When more than one file is involved, grep prints the file name in front of each match so you know where it came from:
$ grep -rn "database_url" /etc/myapp/
/etc/myapp/config.yml:12:database_url: postgres://localhost/app
/etc/myapp/staging.yml:9:database_url: postgres://localhost/staging
The pairing of -r and -n together is the combination you will type most often when working in code.
There is a second recursive flag, -R (short for dereference-recursive). It works like -r but also follows symbolic links into other folders. Prefer plain -r unless you specifically want to chase symlinks, because -R can wander into places you did not mean to search.
5.2 Limiting Which Files to Search
Searching a whole project also digs through images, build output, and the .git folder, which is slow and noisy. Two options keep the search focused:
| Option | What it does |
|---|---|
--include="*.php" |
Search only files whose name matches the pattern |
--exclude="*.min.js" |
Skip files whose name matches the pattern |
--exclude-dir=".git" |
Skip a whole directory by name |
$ grep -rn --include="*.py" --exclude-dir=".git" "import requests" .
./api/client.py:3:import requests
./tests/test_api.py:5:import requests
You can repeat these options as many times as you need. This focused shape is how people search real projects quickly.
5.3 List Only the File Names
When you do not care about the matching line, only which files contain the pattern, use -l (short for files-with-matches, think "list"). It prints each file name once and stops reading that file after the first hit, so it is fast:
$ grep -rl "api_key" .
./config/secrets.yml
./scripts/deploy.sh
The opposite is -L (short for files-without-match), which lists the files that do not contain the pattern. That is handy for finding files missing an expected line, such as a licence header.
5.4 Show Surrounding Lines
A single matching line often does not tell the whole story. Three options add context around each match:
| Option | Meaning |
|---|---|
-A 3 |
Show 3 lines after each match |
-B 2 |
Show 2 lines before each match |
-C 2 |
Show 2 lines of context on both sides |
$ grep -A 4 "Traceback" app.log
Traceback (most recent call last):
File "app.py", line 88, in handle
result = process(data)
File "app.py", line 60, in process
return data["key"]
KeyError: 'key'
Here -A 4 turns a single "Traceback" line into the whole error, so you see what actually went wrong.
5.5 Whole Words and Whole Lines
A search for id also matches video, hidden, and width, because the letters appear inside those words. The -w flag (short for word-regexp) matches only when the pattern stands alone as a whole word:
$ grep -w "id" schema.sql
id INTEGER PRIMARY KEY,
The -x flag (short for line-regexp) is stricter still: the whole line, from start to end, must equal the pattern. This is the way to find an exact value rather than a line that merely contains it:
$ grep -x "PermitRootLogin no" /etc/ssh/sshd_config
PermitRootLogin no
A line reading # PermitRootLogin no would match with -w but not with -x, because the leading comment makes the whole line different.
6. Advanced Use Cases
6.1 Regular Expressions: Describing the Shape of Text
The "re" in the name is the real power. Instead of a fixed word, a regular expression describes the shape of the text you want. A few building blocks cover most needs:
| Pattern | Matches |
|---|---|
^ |
The start of a line |
$ |
The end of a line |
. |
Any single character |
* |
Zero or more of the item before it |
[abc] |
Any one of a, b, or c |
[^abc] |
Any character except a, b, or c |
[0-9] |
Any digit |
$ grep "^root" /etc/passwd # lines that START with root
root:x:0:0:root:/root:/bin/bash
6.2 Basic Versus Extended Regular Expressions
grep has two regex dialects. In the default basic syntax, the characters +, ?, {, |, (, and ) are ordinary text, and you must put a backslash in front of them to make them special. The extended syntax, turned on with the -E flag (short for extended-regexp), makes them special without the backslash, which is far easier to read.
$ grep "colou\?r" file.txt # basic: the ? must be escaped
$ grep -E "colou?r" file.txt # extended: no backslash needed
Both match color and colour. Most people reach for -E as soon as a pattern grows past a plain word. The | character, meaning "or", is the most common reason:
$ grep -E "error|warning|critical" app.log
This finds every line containing any of the three words in a single pass.
There is a second, more portable way to search for several patterns that needs no regex at all. The -e flag (short for regexp) lets you give each pattern separately, and you can repeat it:
$ grep -e "error" -e "warning" -e "critical" app.log
When the list of patterns grows long, put them in a file, one per line, and read them with the -f flag (short for file). This is how you check a log against a whole list of known-bad IP addresses or terms at once:
$ grep -f blocklist.txt access.log
6.3 Quantifiers and Real Patterns
Quantifiers say how many times something may repeat: + means one or more, ? means zero or one, and {2,4} means between two and four times. Put together, they describe real things like an IP address:
$ grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
Read it as: one to three digits followed by a dot, three times over, then one to three more digits.
6.4 Perl-Compatible Regular Expressions
GNU grep offers a third, more powerful engine with the -P flag (short for perl-regexp). It adds shortcuts like \d for a digit and \w for a word character, plus advanced features such as look-ahead:
$ grep -P "\d{4}-\d{2}-\d{2}" app.log # match an ISO date like 2026-07-07
This is a GNU feature and is not present in the BSD grep that ships with macOS, so keep it out of scripts that must run everywhere.
6.5 grep in Pipelines
If you give grep no file name, it reads whatever is piped into it. This turns it into a universal filter for the output of any other command. The pipe symbol | sends one command's output into the next:
$ ps aux | grep "nginx"
www-data 1243 0.0 0.5 nginx: worker process
www-data 1244 0.0 0.5 nginx: worker process
You can chain several stages, each narrowing what the last one kept:
$ grep "Googlebot" access.log | grep " 404 "
The first stage keeps requests from Google's crawler, the second keeps only those that returned a 404. And paired with sort and uniq, grep becomes a small reporting tool:
$ grep -o "GET [^ ]*" access.log | sort | uniq -c | sort -rn | head
842 GET /index.html
317 GET /style.css
96 GET /favicon.ico
The -o flag (short for only-matching) prints just the matched text instead of the whole line, so sort and uniq -c can count how often each URL appears.
6.6 Quiet Mode for Scripts
The -q flag (short for quiet) prints nothing at all. It only reports, through its exit status, whether a match was found. This is how shell scripts ask a yes/no question:
if grep -q "PermitRootLogin no" /etc/ssh/sshd_config; then
echo "Root login is disabled."
fi
grep stops at the first match with -q, so this is also the fastest way to test whether something exists.
7. Something Most Users Do Not Know
7.1 grep Speaks Through Its Exit Code
Every command returns a number when it finishes, and scripts read it to decide what to do next. Most users never notice, but grep uses three distinct values, and knowing them explains a lot of "why did my script do that" moments:
| Exit code | Meaning |
|---|---|
0 |
At least one line matched (success) |
1 |
No line matched (not an error, just nothing found) |
2 |
A real error, for example a file that does not exist |
$ echo hello | grep hi; echo "exit: $?"
exit: 1
The key surprise is that finding nothing is exit code 1, not 0. A pipeline that ends in a grep which happens to match nothing will report failure, even though nothing went wrong. This trips up many shell scripts.
7.2 Why GNU grep Is So Fast
You might expect grep to read every byte of a file. It usually does not. GNU grep uses a family of algorithms, most famously Boyer-Moore, that let it skip ahead: it looks at the pattern, works out how far it can safely jump when a character cannot match, and leaps over large stretches of text without examining them. For a set of fixed strings it uses Aho-Corasick, and for regular expressions it compiles them into a fast state machine. It also avoids breaking the input into lines until it has found a likely match. The result is a tool that often searches faster than the disk can feed it data. This is the reason a grep across a huge tree finishes in the time you expected a spinner.
The fastest way to process text is to not look at most of it. GNU
grep's speed comes from cleverly deciding which bytes it is allowed to ignore.
7.3 Knowing Where grep Stops
Part of expertise is knowing when another tool fits better. grep finds lines; when you want to transform or act on them, its neighbours take over:
| Need | Use | Why |
|---|---|---|
| Find files by name, size, or age | find |
grep searches contents, not the directory tree itself |
| Edit or replace matched text | sed |
grep only reads; sed rewrites |
| Work with columns and do maths | awk |
Understands fields, not just whole lines |
| Search a codebase even faster | ripgrep (rg) |
Respects .gitignore and is recursive by default |
There are also focused relatives worth knowing: zgrep searches inside gzip-compressed logs without unpacking them (with bzgrep and xzgrep doing the same for .bz2 and .xz files), and git grep searches only the files a Git repository tracks, which is very fast and skips build output automatically.
7.4 grep and Binary Files
When grep meets a file that looks binary, such as an image or a compiled program, it does not print the matching bytes, which could scramble your terminal. Instead it prints a single note:
$ grep "libc" /bin/ls
Binary file /bin/ls matches
Two flags control this. The -a flag (short for text) tells grep to treat a binary file as ordinary text and print the matching line anyway. The -I flag (a capital i, short for ignore binary) does the opposite: it skips binary files entirely, which keeps a recursive search over a mixed folder clean and quiet:
$ grep -rI "TODO" . # search the tree, but never open binary files
Back to top8. Best Practices
- Always quote the pattern. Double quotes, or single quotes when the pattern contains a
$, stop the shell from changing your pattern beforegrepsees it. - Reach for
-rntogether in code. Recursive search with line numbers is the everyday combination for finding things in a project. - Narrow big searches. Use
--includeand--exclude-dirso a recursive search stays fast and skips.git, caches, and binaries. - Use
-iwhen case is uncertain and-wwhen you want a whole word. They remove most "why did it match that" surprises. - Use
-Ffor literal text. When the pattern contains dots, slashes, or a version number,-F(fixed strings) avoids treating them as regex. - Switch to
-Efor anything with|,+, or grouping. It keeps patterns readable instead of a wall of backslashes. - Remember the exit code in scripts. Use
-qfor tests, and remember that "no match" is exit code 1, not a crash.
When you need the full detail, the documentation is one command away:
$ man grep # the full manual page
$ grep --help # a quick summary of every option
Back to top9. Common Mistakes
9.1 Four Common Myths
| Myth | Reality |
|---|---|
"grep searches for a plain word." |
By default the pattern is a regular expression; ., *, and [ are special. |
| "Finding nothing means an error." | No match is exit code 1, which is normal, not a failure. |
"-c counts every match." |
It counts matching lines; two hits on one line count once. |
"egrep is the way to do extended regex." |
egrep is deprecated; use grep -E instead. |
9.2 Other Traps to Avoid
- Unquoted patterns. A pattern with a space or a
*can be mangled by the shell. Always quote it. - Special characters matching too much. Searching for
1.5.0also matches1x5y0, because.means "any character". Usegrep -F "1.5.0"for a literal search. - Forgetting
-ron a folder. Pointgrepat a directory without-rand it complains that the target is a directory and searches nothing. - Extended syntax without
-E. A pattern with|or+silently fails to work as intended in basic mode. Add-E, or escape the characters. - Matching part of a word. Searching
idreturnsvideoandwidth. Add-wfor whole words. - Grepping secrets onto the screen. Searching a file for a password prints it in plain text, and it may linger in your shell history. Do it only when you must.
10. Summary
The grep command is how you find things in text on a Linux system, from one error in a giant log to a setting hidden in a tree of config files.
grepis a filter: text flows in, and only the lines matching your pattern flow out.- Its name is an acronym for the
edcommandg/re/p: global, regular expression, print. Ken Thompson wrote it for Unix around 1973. - It is read-only and safe, and it searches files of any size because it works one line at a time.
- Everyday flags:
-i(ignore case),-n(line numbers),-c(count),-v(invert),-w(whole word),-x(whole line). -rsearches a whole tree;--includeand--exclude-dirkeep it fast and focused.- The pattern is a regular expression. Use
-Efor the easier extended syntax and-Ffor a literal string. - With no file,
grepreads a pipe, which makes it the central filter in Linux command chains. - It reports through exit codes 0, 1, and 2, and relatives like
zgrep,git grep, andripgrepextend the same idea. - When in doubt, type
man greporgrep --help.
This is the quick reference worth keeping:
grep "text" file print lines containing text
grep -i "text" file ignore upper/lower case
grep -n "text" file show line numbers
grep -c "text" file count matching lines
grep -v "text" file print lines that do NOT match
grep -rn "text" dir/ search a whole tree, with line numbers
grep -rl "text" dir/ list only the file names that match
grep -w "word" file match whole words only
grep -x "line" file match whole lines only
grep -A3 -B2 "text" f show 3 lines after, 2 before each match
grep -o "text" file print only the matched part
grep -E "a|b|c" file extended regex (alternation, +, ?)
grep -e a -e b file several patterns at once
grep -f pats.txt file read patterns from a file
grep -F "1.5.0" file literal text, no regex
grep -I "text" dir/ skip binary files while searching
grep -q "text" file quiet: exit 0 if found, for scripts
ps aux | grep name filter another command's output
zgrep "text" file.gz search inside a compressed log
Once grep feels natural, a Linux server stops being a black box. You can find a hidden setting, trace an error through a huge log, and pinpoint one line in a forest of code, all in seconds. If your systems are producing logs and files that someone needs to search, understand, and act on, that is exactly the kind of work I help with.


Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.


