When a log file is thousands of lines long, opening it and scrolling usually isn’t the fastest way to find what matters. grep searches file contents for lines matching a pattern—and a few options make the difference between a useful result and a noisy one.
Start with a focused search
grep 'connection refused' app.log
This prints each line in app.log containing the case-sensitive phrase. Add -n to show line numbers, or -i to ignore capitalization:
grep -in 'connection refused' app.log
Quoting the pattern is a good habit, especially when it contains spaces or punctuation. Grep searches contents, not filenames. A wildcard such as *.log is expanded by your shell into a list of filenames before grep runs:
grep -n 'timeout' *.log
That distinction matters: *.log selects which files to search, while the pattern 'timeout' selects matching lines inside them.
Make the pattern mean what you intend
By default, grep treats the pattern as a basic regular expression. That means punctuation can have special meaning. For example, the dot in this pattern matches any single character:
grep 'v1.2' app.log
To search for the literal text v1.2, use fixed-string mode with -F:
grep -F 'v1.2' app.log
For regular-expression alternatives such as “warning or error,” use extended regular expressions with -E:
grep -E 'warning|error' app.log
You can also supply multiple patterns with -e; a line matches if it matches any of them:
grep -e 'failed' -e 'denied' app.log
For a whole-word match, try -w. To match only lines that start with a setting name, use the ^ anchor:
grep -w 'cat' notes.txt
grep '^PermitRootLogin' sshd_config
Add context when one line isn’t enough
A matching log entry may make more sense with the nearby lines. Use -A for lines after a match, -B for lines before it, or -C for context on both sides:
grep -C 2 'failed' app.log
Other useful options change what grep prints:
grep -c 'error' app.log # Count matching lines
grep -v '^#' settings.conf # Print lines that don't match
grep -l 'TODO' *.py # Print filenames with matches
grep -o '[0-9]\+' data.txt # Print matching parts only
-c counts matching lines, not every occurrence of the pattern. If a line contains error three times, it still contributes one to the count.
Search directories, or combine tools
GNU grep can recursively search a directory with -r:
grep -r -n 'TODO' src/
To limit a recursive search to files ending in .conf, GNU grep supports --include:
grep -r --include='*.conf' -n 'listen' .
These filename filters are shell-style patterns, not grep regular expressions. GNU-specific options may not be available in every version of grep, so check man grep when portability matters. Keep the starting directory narrow to avoid noisy searches through generated files or dependencies.
If you need to find files by name or type first, use find to select them and grep to search their contents. The find command guide covers ways to narrow that first step.
find ./config -type f -name '*.conf' -exec grep -nH 'server_name' {} +
Here, find selects regular files with a .conf suffix; grep searches inside them. -H includes filenames in the output.
Use grep with pipelines
Without a filename, grep reads standard input, so it can filter another command’s output:
ps aux | grep 'nginx'
This simple example may match the grep process itself. One common workaround is:
ps aux | grep '[n]ginx'
For process searches, pgrep -a nginx is another option when available. For systemd service logs, grep can filter output from journalctl, though journalctl also has its own search options. See this practical guide to viewing and filtering journal logs.
You can require two terms to appear on the same line by filtering twice:
grep 'database' app.log | grep 'timeout'
The first command passes matching lines to the second, which keeps only those that also contain timeout.
When grep prints nothing
No output doesn’t necessarily mean the command failed. Grep normally exits with status 1 when it completes but finds no matches; status 0 means it found a match. A nonzero result can also indicate an error, so check the path, spelling, capitalization, and permissions before drawing conclusions.
If punctuation is being interpreted unexpectedly, use -F for literal text. If capitalization may differ, use -i. If a pattern starts with a hyphen, mark it explicitly as a pattern with -e:
grep -F -e '-debug' app.log
Grep is most useful when you’re clear about two things: which files to search and what counts as a match. Start with a quoted pattern, add -n for useful locations, and reach for regular expressions only when you need them.
I originally published a more detailed version of this guide on the SSHFlow blog.
I'm also building SSHFlow — an SSH client where every server gets its own workspace for terminals, SFTP, code, and databases.
Top comments (0)