#

Commands

Subscribe
Commands October 29, 2026·10 min read

How to Use grep in Linux: Search, Filter, and Verify Results

Use grep to find matching text in known files, piped output, or a manageable directory tree. Start with the input, use -F for literal strings, and limit which files grep searches when you go recursive. Use find when selecting filesystem paths is the main job, and use ripgrep for code repositories with ignore rules.

What grep searches and your first working command

grep file search

The grep command reads input and prints the lines that match a pattern. Its syntax is grep [OPTIONS] PATTERN [FILE...]. The pattern is the text to find, and the file name says where to look. In a nonrecursive search with no file name, grep reads standard input. The GNU grep manual documents these behaviors. For a repeatable check, use the same sample log for each flag and compare the matching lines and line numbers, not just whether grep returned output.

Add -n when you need the line number, and -F when the pattern is literal text.

The examples use a fictional file tree. Create it in a fresh, disposable directory that has no sample/ folder yet. The commands use >, which overwrites any existing contents of the named files. Later commands assume your terminal is in the parent directory of sample/.

sample/
├── logs/
│   ├── app.log
│   ├── worker.log
│   └── cache/
│       └── old.log
├── node_modules/
│   └── lib.log
└── src/
    └── server.js

Make the directories and populate the five files:

mkdir -p sample/logs/cache sample/node_modules sample/src
printf 'INFO boot\nERROR database unavailable\nWARN retry\nerror timeout\n' > sample/logs/app.log
printf 'INFO startup\nERROR worker stalled\n' > sample/logs/worker.log
printf 'ERROR stale cache\n' > sample/logs/cache/old.log
printf 'ERROR dependency\n' > sample/node_modules/lib.log
printf 'const error = new Error("failure");\n' > sample/src/server.js

Search the application log:

grep -nF 'ERROR' sample/logs/app.log

Output:

2:ERROR database unavailable

Grep printed line 2 of app.log.

What counts as a match

grep matching patterns

GNU grep reads your pattern as a basic regular expression by default. Use -F for a literal string, especially when the text contains punctuation that regex treats as special.

Add -i for a case-insensitive search. The sample log contains both an uppercase and a lowercase error marker:

grep -iF 'error' sample/logs/app.log

Output:

ERROR database unavailable
error timeout

Two options make a match stricter:

  • -w: Matches whole words only, by grep's word-boundary rules.
  • -x: Selects a line only when the whole line matches the pattern.

-E turns on extended regex syntax, which gives you alternation:

grep -E 'ERROR|WARN' sample/logs/app.log

Output:

ERROR database unavailable
WARN retry

JavaScript uses \d for a digit, but a GNU grep expression needs [[:digit:]]. PCRE mode has its own syntax and differs by implementation.

printf 'HTTP 404\n' | grep -E '[[:digit:]]+'

Output:

HTTP 404
grep regex selection

With non-ASCII characters, locale affects case conversion and character classes. The examples here use ASCII text and GNU grep behavior.

Search files, directories, and piped output

grep search scope

To search one file, name it:

grep -F 'ERROR' sample/logs/app.log

To search two files and see which one contains each match:

grep -nF 'ERROR' sample/logs/app.log sample/logs/worker.log

Output:

sample/logs/app.log:2:ERROR database unavailable
sample/logs/worker.log:2:ERROR worker stalled

To search every file under a directory, turn on recursion:

grep -rF 'ERROR' sample/

This covers every file under sample/, including the dependency log in node_modules.

A Linux Academy learner in an r/linux4noobs discussion struggled with recursive searches: “I was using the grep -r unsuccesfully, i dont still understand why”

recursive directory search

Relative paths start from your shell's working directory. Searching sample/logs/ starts in that folder. Searching . starts at the current directory and can cover a much larger tree.

GNU grep's -r follows symlinks you name on the command line but skips symlinks it finds while searching subfolders. -R follows those too, which widens the search.

To filter the output of another command, pipe it into grep:

printf 'INFO\nERROR\n' | grep -F 'ERROR'

Output:

ERROR

Pick the output: lines, filenames, context, or counts

grep output options
OptionWhat it returnsUseful for
-nMatching lines with line numbersLocating an entry inside a file
-lNames of files with selected matchesFinding files that need inspection
-LNames of files without selected matchesFinding files missing a marker
-cNumber of selected lines per fileCounting qualifying lines
-oIndividual nonempty matching substringsExamining the exact matched text
-C NMatches with N lines of context on each sideReading nearby log entries
-qNo normal matching output. Result through exit statusTesting a condition in a script

On the sample log, this command counts two lines. -c adds one per matching line, even if the text appears several times in it.

grep -icF 'error' sample/logs/app.log

Output:

2

To get file names instead, use -l. This version searches only .log files and skips the dependency and cache directories:

grep -rilF --include='*.log' \
  --exclude-dir=cache \
  --exclude-dir=node_modules \
  'error' sample/

Matching filenames:

sample/logs/app.log
sample/logs/worker.log

Recursive traversal can list these files in a different order.

grep results context

-C 1 adds one line before and one after each match, where those lines exist.

Exclude matching lines, files, or directories

grep exclude directories
What to excludeGNU grep selectorEffect
Lines containing INFO-vF 'INFO'Selects lines without that literal string
Files ending in .tmp--exclude='*.tmp'Skips matching filenames
Files outside the .log set--include='*.log'Restricts eligible filenames
Directories named cache--exclude-dir=cacheSkips discovered directories matching that name
One specific nested subtreeNarrow the starting path or select files with findAllows path-specific file selection

-v selects lines that don't match the pattern. Grep still reads every file it searches. File name filters use globs, and content patterns follow the mode you chose, fixed-string or regex. When several include and exclude rules match the same file, the last matching rule wins.

To exclude the INFO line from the application log:

grep -vF 'INFO' sample/logs/app.log

Output:

ERROR database unavailable
WARN retry
error timeout

Combine the options for a case-insensitive search limited to .log files that skips node_modules and cache:

grep -rinF --include='*.log' \
  --exclude-dir=node_modules \
  --exclude-dir=cache \
  'error' sample/

Output from the sample tree:

sample/logs/app.log:2:ERROR database unavailable
sample/logs/app.log:4:error timeout
sample/logs/worker.log:2:ERROR worker stalled

The cache log, dependency log, and JavaScript file stay out of the results.

nested exclusion example

A nested exclusion fails when the pattern is a path instead of the directory name GNU grep compares. This command tries to exclude logs/cache:

grep -rinF --include='*.log' \
  --exclude-dir='logs/cache' \
  'error' sample/

Among its results is:

sample/logs/cache/old.log:1:ERROR stale cache

During recursion, GNU grep compares the basename of each subdirectory it finds with the --exclude-dir pattern. The basename of sample/logs/cache/ is cache, so logs/cache never matches. This command also searches node_modules, since nothing excludes it.

--exclude-dir=cache skips every directory with that basename anywhere in the tree.

Match several patterns without complicated expressions

multiple grep patterns

To match either of two patterns, repeat -e or use alternation with -E. Both commands return the uppercase ERROR and WARN lines from the sample:

grep -e 'ERROR' -e 'WARN' sample/logs/app.log
grep -E 'ERROR|WARN' sample/logs/app.log

The quoted | inside the extended regex means alternation. A shell pipe is a different thing: it connects one command's output to another command's input.

To require both ERROR and database on the same line, apply two filters in sequence:

grep -F 'ERROR' sample/logs/app.log | grep -F 'database'

A grouped condition such as (Company1 AND 2010 OR Company2 AND 2020) AND Copyright is easier to inspect as a structured awk expression or a short script than as a complex regex that needs extra engine features.

Read grep's exit status in scripts

grep exit codes

The exit status tells you whether grep found a match. Empty output doesn't, because it has several possible causes: quiet mode, no matching lines, or an input error.

Exit statusOrdinary GNU grep meaningInterpretation
0A line was selectedA match was found
1No line was selectedThe searched input produced no match
2An error occurredInspect the reported failure

Other grep implementations can return an error status greater than 2.

Run a quiet search for a string that isn't in the sample file:

grep -qF 'NOT_PRESENT' sample/logs/app.log
echo "$?"

Output:

1

$? holds the exit status of the previous command. Read it right after grep, because the next command overwrites it.

grep script verification

To use the result in a conditional:

if grep -qF 'ERROR' sample/logs/app.log; then
    printf 'Error marker found\n'
fi

With -q, GNU grep can return 0 on a match even if it hit an error elsewhere in the same run. A 0 means something matched, not that grep could read every file.

-s hides some error messages, including those for unreadable or nonexistent files. The problem with the input is still there.

Diagnose missing results, excessive output, and slow searches

grep troubleshooting checks
  • No output with status 1: Grep read the input and found no matching lines. Test your pattern against the sample's ERROR entry, then check the path, capitalization, fixed-string or regex mode, and any include or exclude filters.
  • An error message or error exit status: Check the working directory and the file name. A misspelled path, an unreadable file, or an unavailable directory needs an input fix, so keep stderr visible while you debug.
  • Unexpected files or far too many lines: Read the file names in the results. A starting directory that's too broad, a wrong glob in a file name filter, or a directory you forgot to exclude explains irrelevant matches. In the sample tree, leaving out --exclude-dir=node_modules adds sample/node_modules/lib.log to the results.
  • Very slow traversal: Narrow the search to the relevant directory or fewer files, for example with --include. -R follows extra symlinks, and a needlessly complex regex adds work.
binary input handling

In a tree that mixes text and binary files, GNU grep's -I treats binary files as having no matches, so only text files contribute results. -a treats binary input as text and prints binary bytes your terminal can't display.

Which tool fits: grep, find, or ripgrep

linux search tools
ToolMain jobAppropriate situation
grepSelect matching text from files or standard inputA known log, configuration file, or chosen directory tree
findSelect filesystem entries by path and attributesA specific nested subtree or files with particular properties
ripgrep (rg)Recursive text search with repository-oriented filteringSource code where project ignore rules matter

find can give grep a precisely selected set of file paths. Use it when one specific nested path must be included or excluded and grep's filename globs aren't precise enough.

In a Git repository, ripgrep applies .gitignore rules during recursive searches, and it also reads .ignore and .rgignore files. By default it skips hidden entries and binary files and doesn't follow symlinks. Options can change each of these, so a ripgrep search can cover a different set of files than grep -r.

ripgrep compatibility choices

A Fedora user with Heroku CLI tab-completion trouble wrote in an r/commandline discussion: “The root cause? I had alias grep='rg' in my bashrc.” Swapping rg in for grep changes the options it accepts and the files it searches.

NoScript Team