Skip to content

Searching Inside Files


cat and less show you a file’s contents, but neither one can answer a question as basic as “does this file mention the word ’error’ anywhere?” without you reading every line yourself. grep is the tool for that — and it’s one you’ll reach for constantly, whether you’re hunting through a single file or piping a command’s output straight into it.

The Basic Shape

grep searches for a pattern inside a file (or files) and prints every line that matches.

grep "error" logfile.txt

This prints every line in logfile.txt containing the text error, exactly as it appears. Nothing fancy yet — but even this basic form is doing more than it looks like, because “error” isn’t just a literal string to grep. It’s a pattern, and patterns are where grep earns a whole file to itself.

Matching is Case-Sensitive By Default

grep "error" logfile.txt will not match a line containing Error or ERROR — only the exact case you typed. Add -i to ignore case entirely:

grep -i "error" logfile.txt

This is worth reaching for by default when you’re not certain how something was capitalized in the file you’re searching, which in practice is most of the time.

Useful Flags Beyond Matching Text

A handful of flags come up constantly enough to know before we even get to patterns:

FlagEffect
-iCase-insensitive matching
-vInvert the match — show lines that don’t match
-nShow line numbers alongside each match
-rSearch recursively through a directory
-cPrint a count of matching lines instead of the lines themselves
-lPrint only the names of files with at least one match, not the matches themselves

-v in particular is easy to forget exists and surprisingly useful — it’s how you answer “show me every line that isn’t a comment” or “show me every process that isn’t the one I’m looking for,” rather than only ever searching for what you want to find.

grep -v "^#" config.txt

That example uses a ^ — which brings us to the actual reason grep gets its own chapter.

Patterns, Not Just Text: An Introduction To Regular Expressions

By default, grep treats your search term as a regular expression (often shortened to “regex”) — a mini pattern language, not just literal text. Most of the time a plain word behaves exactly like you’d expect, which is why the examples above worked without needing any explanation. But a handful of characters mean something special, and knowing even a handful of them turns grep from “find this exact word” into “find anything shaped like this.”

SymbolMeaningExampleMatches
^Start of line^errorLines starting with “error”
$End of lineerror$Lines ending with “error”
.Any single charactererr.r“error”, “errxr”, “err5r”
*Zero or more of the previous charactererr*or“eor”, “error”, “errrror”
[...]Any one character from this set[Ee]rror“Error” or “error”

A few of these deserve a closer look, because they’re not as intuitive as they first seem.

^ and $ anchor a match to a position, not a character. ^error doesn’t mean “error near the start” — it means the very first characters on the line must be exactly error. This is why the -v "^#" example above works as a way to skip comment lines: it excludes any line where # is the first character, without caring what comes after it.

. matches literally any character — including a period. This trips people up constantly when searching for something like an actual file extension or IP address. grep "192.168.1.1" will also match 192x168y1z1, because unescaped, . means “any character,” not “a literal dot.” To match a literal period, escape it with a backslash: 192\.168\.1\.1.

* applies to the character immediately before it, not to the whole pattern. err*or means “err, then zero or more of the letter r, then or” — matching “eor” (zero extra r’s) just as validly as “error” (one extra r) or “errror” (two extra r’s). This is the single most common regex misunderstanding: people expect * to mean “anything,” but on its own it only repeats whatever came immediately before it.

Tip

If you want “match anything, any length,” that’s actually the combination .* — “any character” followed by “zero or more of it.” grep "start.*end" matches any line containing “start” followed eventually by “end,” with anything at all in between. This combination comes up so often it’s worth memorizing as a pair.

grep and Extended Regular Expressions

If you are using grep to match Extended Regular Expressions (just some more character set with special meaning), you should specify -E option to say Hey! go beyond basic regular expression, search with extended regular expression.

EREMeaningExampleMatches
()Grouping(ab)+ab, abab, ababab
{}Repetition counta{3}aaa
?Zero or one occurrencecolou?rcolor, colour
+One or more occurrencesa+a, aa, aaa

Example:

grep colou?r file

Matches literal ? but the following:

grep -E colou?r file

As -E is specified, now ? has different meaning i.e. Match Zero or one occurrence of preceding character or group and in this case the preceding charater is u.

Quoting Your Pattern

You’ve probably noticed every example wraps the pattern in double quotes. This isn’t just style — it matters. Left unquoted, the shell itself tries to interpret characters like * and $ before grep ever sees them, which can silently change what you’re actually searching for depending on what files happen to be in your current directory. Quoting the pattern guarantees grep receives exactly what you typed.

Searching Multiple Files And Whole Directories

Give grep more than one file and it searches all of them, prefixing each match with the filename it came from:

grep "error" logfile1.txt logfile2.txt

To search every file in a directory, recursively, add -r:

grep -r "TODO" projects/

This walks into every subdirectory of projects/, checking every file it finds, and reports the filename and matching line for each hit — one of the most common ways grep actually gets used day-to-day, especially combined with -n to jump straight to the right line once you know which file to open.

Piping Into grep

Remember pipes from the previous chapter? This is where they become genuinely essential. Instead of searching a file, you can search any command’s output by piping it into grep:

ls -la /etc | grep "conf"

This lists /etc, then filters that listing down to only lines containing “conf” — without grep ever needing to know or care that its input came from ls instead of a file. You’ll use this pattern relentlessly once you’re inspecting running processes or reading logs later in this course: run a command that produces a lot of output, then pipe it into grep to narrow it down to the one thing you actually care about.

    flowchart LR
    A["ls -la /etc"] -->|"full listing"| B["grep 'conf'"]
    B --> C["Only lines containing 'conf'"]
  

What’s Next

grep finds lines inside files that match a pattern. But it can’t replace what it finds — you need another tool. Also it has no idea how to find files themselves scattered across a directory tree based on their name, size, or age — that’s a different problem, with a different tool. So in next few chapters, we will be talking about those.

Last updated on