find locates files by name. grep finds the text inside them. Sooner or later every Linux user hits the same wall: you know a setting, an error message, or a line of code exists somewhere on the machine, but you have no idea which file holds it. Opening files one by one is not a plan. grep is.

The name comes from an old editor command, g/re/p (globally search a regular expression and print), and the tool has been doing exactly that since the 1970s. It is installed on practically every Linux system by default, it works the same on a laptop and a server, and once the handful of flags below become muscle memory, searching text stops being a chore and starts feeling like a superpower.

Prerequisites

You need a terminal and a file to practice on. If you want to follow along exactly, create a small sample file first:

BASH
cat > ~/notes.txt <<'EOF'
Buy milk and coffee beans
Call the bank about the mortgage
Fix the printer on the second floor
Read the Linux kernel changelog
Buy tickets for the Friday show
EOF

Everything below runs against that file, so you can see each flag change the output in real time.

1. The basic search

The syntax is refreshingly simple:

BASH
grep pattern file

Try it:

BASH
grep Buy ~/notes.txt

grep prints every line containing Buy. Two things to notice: the match is case-sensitive (buy would find nothing here), and grep prints the whole line, not just the matched word. That is the whole point: you usually need the context around a match, not the match alone.

Search several files at once by listing them, and grep prefixes each hit with the file name:

BASH
grep Buy ~/notes.txt /etc/hostname

2. The four flags you will use every day

Most real-world searching is done with four flags. Learn these and you cover 90 percent of cases:

BASH
grep -i buy ~/notes.txt        # -i: ignore case
grep -w Buy ~/notes.txt        # -w: whole word only
grep -v Buy ~/notes.txt        # -v: invert, show lines WITHOUT the match
grep -n Buy ~/notes.txt        # -n: show line numbers

The -w flag matters more than it looks. Searching for cat without it also matches catalog, concatenate, and education. Searching for error without it matches errors too, which is usually what you want, but when it is not, -w is the fix.

Combine flags freely. grep -in is case-insensitive search with line numbers, and it is probably the single most typed grep invocation on any Linux box:

BASH
grep -in "kernel" ~/notes.txt

3. Search a whole directory, recursively

A single file is the easy case. The real job is usually "somewhere in this project" or "somewhere in this config directory". That is what -r is for:

BASH
grep -r "TODO" ~/projects/

grep walks every file under the directory and prints matches with their paths. Add --include to narrow it to one file type, which is how you avoid wading through minified JavaScript or binary blobs:

BASH
grep -r --include="*.py" "TODO" ~/projects/
grep -r --include="*.conf" "listen" /etc/nginx/

Two more output flags are worth knowing. -l prints only the names of files that contain a match, which is what you want when the question is "which files mention this", not "show me the lines". -c counts matches per file:

BASH
grep -rl "TODO" ~/projects/     # just the file names
grep -rc "TODO" ~/projects/     # how many hits per file

4. A little regex goes a long way

grep patterns are regular expressions, not plain text. You do not need to master regex to benefit; three constructs cover most daily needs:

BASH
grep -E "^Buy" ~/notes.txt        # ^ anchors to the start of a line
grep -E "floor$" ~/notes.txt      # $ anchors to the end of a line
grep -E "Buy|Call" ~/notes.txt    # | means "or" (needs -E)

The -E flag switches on extended regular expressions, which is what makes | and + work without backslashes. A dot matches any single character, so gr.p matches grep, grip, and grasp. A character class like [0-9] matches any single digit, which is handy for hunting version numbers or IP addresses in logs:

BASH
grep -E "192\.168\.[0-9]+\.[0-9]+" /var/log/syslog

Note the backslash before each literal dot: an unescaped dot matches any character, including a dot, so 192.168 would also match 192x168. That is the single most common regex mistake in grep usage, and it produces results that look right until they quietly are not.

If a pattern contains spaces, quote it. Single quotes are safest, because the shell passes the pattern to grep untouched:

BASH
grep "second floor" ~/notes.txt

5. grep is a filter, not just a searcher

Half of grep's value has nothing to do with files. Pipe any command's output into it and it becomes a filter:

BASH
ps aux | grep firefox
ss -tulpn | grep 8080
history | grep "git push"

This is the idiom you will reach for when reading logs. A service that writes thousands of lines a minute becomes readable the moment you filter it:

BASH
journalctl -u nginx --since today | grep -i error

Pairing grep with the journal is a daily habit worth building, and if you have not yet made friends with journalctl, our guide to reading Linux logs with journalctl walks through the flags that matter.

6. When grep is not enough: ripgrep

On a small directory, grep -r is instant. On a large codebase with thousands of files, a node_modules folder, and a .git directory, it starts to crawl, because it searches everything, including files you never want to search. That is the problem ripgrep (rg) was built to solve. It respects .gitignore by default, skips hidden files and binary files, and searches in parallel. On a typical project it is several times faster, and the output is easier to read.

Install it from your distro's repositories:

BASH
sudo apt install ripgrep      # Debian / Ubuntu
sudo dnf install ripgrep      # Fedora
sudo pacman -S ripgrep        # Arch

The flags are mostly the same, so your grep muscle memory transfers directly:

BASH
rg "TODO" ~/projects/
rg -i "error" /var/log/
rg --type py "def main" ~/projects/

Should you replace grep entirely? No. grep is everywhere, including minimal containers and rescue environments where rg is not installed. Learn grep first, reach for rg when a recursive search feels slow or noisy, and keep both in your toolkit. They are complements, not rivals.

Common pitfalls

Searching from / and drowning in permission errors. grep -r "foo" / will hit system directories you cannot read and print a wall of Permission denied. Either scope the search to a directory you own, or silence the errors and keep the results: grep -r "foo" /etc 2>/dev/null. If you keep running into permission walls, it is worth understanding what the permissions actually mean, which our guide to Linux file permissions explains from the ground up.

Quoting the pattern wrong. grep Buy milk notes.txt searches for Buy in two files named milk and notes.txt. Multi-word patterns always need quotes.

Forgetting that grep reads stdin when no file is given. Run bare grep foo with no file and it sits there waiting for input, which looks like a hang. Type a line, press Enter, and it filters it. Press Ctrl+C to escape.

Binary files. Searching a compiled program or a compressed log produces Binary file matches (uses binary data). Add -a to force text mode, or -I to skip binary files entirely.

Verify it yourself

Run this quick self-test to confirm the workflow end to end:

BASH
grep -rin "buy" ~/notes.txt
grep -r --include="*.txt" "TODO" ~/ 2>/dev/null | head
rg --version || echo "ripgrep not installed yet"

The first command should show two matches with line numbers, case-insensitively. The second shows how a scoped recursive search behaves on your real home directory. The third tells you whether rg is ready when you need it.

If you are still building your command-line habits, grep pairs naturally with find: one finds files by name, the other finds text inside them. Our guide to finding any file on Linux covers the find side, and the command-line basics weekend guide ties the whole toolkit together.

Sources