PLAINFINCH learn by building

Searching text with grep

You will search files for a word, ignore differences in capital letters, and see which lines and files contain it.

What does grep do?

grep reads text and prints every line that contains a pattern you give it. You name the pattern first and the files second. It never changes the files; it only reads them and reports what it found. That makes it one of the safest commands you can run, and one of the most used.

The bird club keeps one sightings file per month. To see every heron in January you ask grep for the word and the file:

Two lines come back, the grey heron and the plain heron, because both contain the letters heron. Lines that don't match are left out. If nothing matches, grep prints nothing at all and the prompt comes straight back. That silence is an answer too: the word isn't there, at least not in the form you typed.

grep heron sightings/jan.txt
2026-01-04 grey heron Mill Pond
2026-01-18 heron Mill Pond

Why does case trip you up, and what does -i do?

By default, grep is case-sensitive. kingfisher, Kingfisher and KINGFISHER are three different strings to it. Data typed by different people always has this problem: the February file has a capital K and someone typed the March entry with caps lock on.

Add -i (ignore case) and all three count. Here grep also gets three files at once through the wildcard sightings/*.txt. When grep searches more than one file, it puts the file name in front of each line so you know where it came from:

Use -i whenever people typed the text by hand: notes, sightings, order comments. Leave it off when case carries meaning, such as in code, where Total and total can be different variables.

grep -i kingfisher sightings/*.txt
sightings/feb.txt:2026-02-01 Kingfisher Weir
sightings/jan.txt:2026-01-04 kingfisher River Walk
sightings/mar.txt:2026-03-08 KINGFISHER River Walk

How do you see line numbers and lines that don't match?

-n adds the line number after the file name. That tells you exactly where to look when you open the file to fix something. On a real machine nano +2 sightings/mar.txt opens the file with the cursor on line 2.

Options can be written separately (-i -n) or squeezed together (-in). Both mean the same thing, and the order of the letters doesn't change the result.

-v turns the search inside out and prints every line that does NOT match. It's handy for clearing noise: hide every Mill Pond sighting to see what turned up elsewhere, or hide comment lines in a settings file with grep -v '^#' settings.conf.

grep -in kingfisher sightings/*.txt
sightings/feb.txt:1:2026-02-01 Kingfisher Weir
sightings/jan.txt:1:2026-01-04 kingfisher River Walk
sightings/mar.txt:2:2026-03-08 KINGFISHER River Walk

grep -v "Mill Pond" sightings/mar.txt
2026-03-08 KINGFISHER River Walk
2026-03-14 water rail Reed Bed
2026-03-21 robin Mill trail
FigureOne line of grep -in kingfisher sightings/*.txt output split into three parts: the file name sightings/mar.txt, the line number 2, and the matching line itself with KINGFISHER in capitals.One line of grep -in kingfisher sightings/*.txt output split into three parts: the file name sightings/mar.txt, the line number 2, and the matching line itself with KINGFISHER in capitals.
With several files and -n, each match carries its own address: file, then line number, then the text. nano +2 sightings/mar.txt opens that exact line on a real machine.

When do you need quotes around the pattern?

The shell splits what you type at every space before grep sees it. Type grep grey heron sightings/jan.txt and grep receives three separate words: the pattern grey, then two file names, heron and sightings/jan.txt. It complains that there is no file called heron. grep still goes on to search sightings/jan.txt for grey and prints that line with the file name in front, which can look as if it half worked.

Put quotes around any pattern with a space in it. Double quotes work for ordinary words. Single quotes are safer when the pattern contains $, *, ! or a backslash, because the shell leaves everything inside single quotes alone. Patterns with characters like *, ? or [ need quotes even without a space, or the shell may treat them as a wildcard and swap in file names before grep runs.

A good habit is to quote every pattern that is more than one plain word. It costs two keystrokes and removes a whole class of confusing errors.

grep grey heron sightings/jan.txt
grep: heron: No such file or directory
sightings/jan.txt:2026-01-04 grey heron Mill Pond

grep "grey heron" sightings/jan.txt
2026-01-04 grey heron Mill Pond

How do you search a whole folder?

Give grep a folder name and it refuses with grep: sightings: Is a directory. It only reads files unless you tell it to go inside folders. You have two ways to do that.

A wildcard such as sightings/*.txt lets the shell list the files for grep. That works for one level and lets you pick a file type. -r (recursive) tells grep to walk through the folder and every folder inside it, reading each file it finds. Use -r when you don't know exactly where something is, for example to find which page of a website still mentions last year's opening hours.

-r can be slow and noisy on big folders. Pointing it at the right folder helps more than anything: search sightings, not your whole home folder. On a real website project, avoid running it over folders like node_modules, which hold thousands of files you didn't write.

grep -rin kingfisher sightings
sightings/feb.txt:1:2026-02-01 Kingfisher Weir
sightings/jan.txt:1:2026-01-04 kingfisher River Walk
sightings/mar.txt:2:2026-03-08 KINGFISHER River Walk

How do you count matches or list only the file names?

Sometimes you don't want the lines, only a summary. -c prints how many lines matched in each file. -l prints only the names of files that contain at least one match, one per line.

Note what -c counts: lines, not words. A line that mentions heron twice still counts once. And -c with several files gives one count per file; the next lessons show how to add them up with a pipe and wc -l.

-l is the one to use before changing files. If you plan to edit every page that mentions an old phone number, list them first with grep -rl and read the list. It tells you how big the job is and catches files you didn't expect.

grep -c heron sightings/*.txt
sightings/feb.txt:1
sightings/jan.txt:2
sightings/mar.txt:1

grep -l "grey heron" sightings/*.txt
sightings/feb.txt
sightings/jan.txt

Why does grep find words inside other words?

The grep command matches letters in a row, wherever they appear. Search for rail and you get the water rail, and also both robins on the Mill trail, because trail contains r-a-i-l. Search for tit in a list of birds and you get titmouse and bushtit too.

-w (word) only matches the pattern when it stands on its own, with a space, punctuation or the edge of the line on either side. grep -w rail keeps the water rail and drops the trails.

When a search returns more than you expected, read the extra lines and ask what the pattern is accidentally matching. Usually -w, a longer pattern or a quoted phrase sorts it out.

grep rail sightings/*.txt
sightings/feb.txt:2026-02-22 robin Mill trail
sightings/mar.txt:2026-03-14 water rail Reed Bed
sightings/mar.txt:2026-03-21 robin Mill trail

grep -w rail sightings/*.txt
sightings/mar.txt:2026-03-14 water rail Reed Bed

What is different on a real machine?

The everyday options here (-i -n -v -c -r -l -w) behave the same in GNU grep on Linux and BSD grep on a Mac. Real grep also understands patterns called regular expressions, where characters like ^, $, . and * have special meanings. ^2026-03 means a line that starts with that date, for example. -E turns on the extended form, where heron|egret finds either word.

Some details differ from this practice terminal. Real grep -r over . prints names like ./sightings/jan.txt with a leading ./; this terminal leaves it off. Real grep also colours the matched words on most systems. The order of files in -r output can vary on a real disk, so don't rely on it.

Many developers install ripgrep (the rg command), which searches folders by default and skips files listed in .gitignore. Its options look similar but aren't identical, so check them before copying a grep command across.

What do AI tools get wrong with grep?

AI suggestions often forget quotes around a multi-word pattern, or wrap a pattern containing $ in double quotes so the shell swaps in a variable first. If a suggested search finds nothing, print the pattern with echo to see what the shell actually passes.

They also reach for -r on your whole home folder, mix up -l and -c, or assume grep -P (Perl-style patterns), which is missing from the grep that ships on a Mac. Read each option letter and check it against grep --help before you trust the output.

Field notes

  • grep pattern file prints each line that contains the pattern.
  • -i ignores case, so Kingfisher and KINGFISHER both match.
  • -n adds line numbers; -l lists only the names of matching files.
  • Put quotes around a pattern that contains a space.

Easy to get wrong

Without quotes the shell splits the phrase, so grep searches for grey and treats heron as a file name that doesn't exist.

grep reads files. Given a folder, it says Is a directory until you add -r or use a wildcard like sightings/*.txt.

grep is case-sensitive, so it misses Kingfisher and KINGFISHER. Add -i for anything people typed by hand.

grep matches letters anywhere in a line, so rail also finds trail. -w matches whole words only.

Questions people ask

Does grep change my files?

No. grep only reads. It's safe to run on anything, including live website files on a server.

What does grep stand for?

It comes from an old editor command, g/re/p: globally search for a regular expression and print the matching lines. Most people never think of the name again.

Why does grep print nothing?

Nothing matched. Check the spelling, try -i for case, and check you gave it the right file with ls. If you see no prompt at all on a real machine, grep may be waiting for input because you left out the file name; press Ctrl+C.

How do I search for a word that starts with a dash?

Put -- before it: grep -- -v notes.txt. The -- tells grep that options have ended, so -v is read as the pattern.

Can grep search Word documents or PDFs?

Not usefully. Those files are packed binary formats, so grep either finds nothing or says the binary file matches. It works on plain text: .txt, .csv, .html, .css, .js, logs and settings files.

Keep going