Searching Text with grep
When you run servers, you spend a huge amount of time looking for one line inside thousands of lines of text. Maybe an error in a log file, a setting in a config file, or a process in a list. grep is the tool that finds it. The name stands for “global regular expression print” (a search command that prints matching lines), and it is one of the most-used commands in a DevOps engineer’s day. Once you are comfortable with grep, scanning a 50,000-line log feels as fast as reading one sentence.
What grep does
grep reads text line by line and prints only the lines that match a pattern you give it. The text can come from a file, many files, or from the output of another command piped into it. A “pattern” can be a simple word like error, or a “regular expression” (regex) — a small language for describing text shapes, like “a line that starts with a number” or “an IP address”.
The basic shape of the command is:
grep "pattern" filename
For example, to find every line containing the word denied in the authentication log on Ubuntu:
grep "denied" /var/log/auth.log
Output:
Jun 15 09:14:02 web01 sshd[2841]: Permission denied, please try again.
Jun 15 09:14:08 web01 sshd[2841]: Permission denied, please try again.
Ubuntu 22.04 and 24.04 LTS store most system logs under
/var/log. Many of these files (likeauth.logandsyslog) needsudoto read, so prefix the command withsudoif you see “Permission denied” fromgrepitself.
The flags you will use every day
A few options turn plain grep into a precision tool. These are worth memorising.
| Flag | What it does | When to use it |
|---|---|---|
-i | Ignore case (treat Error, ERROR, error the same) | When you are not sure how a word is capitalised |
-r | Recursive — search every file in a folder and its subfolders | Searching a whole config directory like /etc/nginx |
-n | Show the line number of each match | When you need to jump straight to the line in an editor |
-v | Invert — show lines that do NOT match | Filtering out noise you do not care about |
-c | Count matching lines instead of printing them | Quick “how many times did this happen?” checks |
-w | Match whole words only | Avoid matching error inside errored or mirror |
-l | List only the file names that contain a match | ”Which file has this setting?” across many files |
Case-insensitive search with -i
Logs and config files are inconsistent about capitals. Use -i so you do not miss anything:
grep -i "error" /var/log/syslog
This matches Error, ERROR, and error all at once.
Recursive search with -r
To find which Nginx config file contains a server_name line, search the whole directory:
grep -rn "server_name" /etc/nginx/sites-available
Output:
/etc/nginx/sites-available/devcraftly.conf:8: server_name devcraftly.com www.devcraftly.com;
/etc/nginx/sites-available/api.conf:6: server_name api.devcraftly.com;
Here we combined -r (recursive) and -n (line numbers). Combining flags like this is normal and encouraged.
Line numbers with -n
When grep tells you devcraftly.conf:8, you know the match is on line 8. You can open it straight there in nano with nano +8 /etc/nginx/sites-available/devcraftly.conf or in vim with vim +8 <file>.
Inverting the match with -v
Sometimes it is easier to hide the lines you do not want. To see a config file without blank lines and without comment lines (lines starting with #):
grep -v "^#" /etc/nginx/nginx.conf | grep -v "^$"
The first grep -v "^#" drops comment lines; the second drops empty lines (^$ means “start then immediately end”). This is a clean way to see only the settings that are actually active.
Piping output into grep
grep becomes really powerful when you feed it the output of another command using a pipe (the | symbol, which sends one command’s output into the next). This lets you filter any command’s output.
Find a running process by name:
ps aux | grep nginx
Output:
root 812 0.0 0.1 55180 1740 ? Ss 09:02 0:00 nginx: master process
www-data 813 0.0 0.3 55620 6204 ? S 09:02 0:00 nginx: worker process
Check whether a port is listening:
sudo ss -tlnp | grep ":443"
Filter the last lines of a live log as they arrive:
sudo tail -f /var/log/nginx/access.log | grep " 500 "
The tail -f part follows the file in real time, and grep shows you only requests that returned HTTP status 500 (a server error). This is a classic way to watch for problems while you reproduce a bug.
Searching logs for errors
This is the bread-and-butter use of grep. A typical investigation looks like this. First, count how many errors happened today:
sudo grep -ic "error" /var/log/syslog
Output:
27
Then look at the actual lines with context. The -A, -B, and -C flags show lines After, Before, or around (Context) each match:
sudo grep -i -C 2 "error" /var/log/syslog
-C 2 prints the 2 lines before and after every match, which is often where the real cause is.
For systemd services (the system that starts and supervises background programs on Ubuntu), you usually search the journal instead of a file:
sudo journalctl -u nginx --since today | grep -i "fail"
A gentle introduction to regex
A regular expression describes a text pattern rather than exact words. You do not need all of it, but a handful of symbols cover most daily needs:
| Pattern | Meaning | Example match |
|---|---|---|
^ | Start of a line | ^Jun matches lines starting with Jun |
$ | End of a line | failed$ matches lines ending in failed |
. | Any single character | b.t matches bat, bit, bot |
[0-9] | Any one digit | [0-9][0-9] matches 42 |
* | Zero or more of the previous item | ab* matches a, ab, abbb |
| | OR (with -E) | warn|error matches either word |
Basic grep understands the simple symbols above. For OR, grouping, and +, use “extended regex” with the -E flag (or the command egrep, which is the same thing):
grep -E "warn|error|critical" /var/log/syslog
Find lines that look like an IP address:
grep -E "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" /var/log/nginx/access.log
Here [0-9]+ means “one or more digits” and \. means a literal dot (the backslash escapes the dot so it is not treated as “any character”).
Watch out: an unquoted pattern with special characters like
*or$can be changed by the shell beforegrepever sees it. Always wrap your pattern in double quotes, likegrep "error$" file, to be safe.
Best practices
- Always quote your pattern (
grep "pattern" file) so the shell does not mangle special characters. - Reach for
-iby default when searching logs — capitalisation is rarely consistent. - Combine
-rnto find both the file and the exact line in one shot across a directory. - Use
-C 2(or-A/-B) on errors to capture the surrounding lines that explain the cause. - Prefer
grep -Efor OR and grouping instead of fragile escaping. - Use
-vto strip out known noise, and-cfor a fast count before diving into details. - Remember
sudofor protected files under/var/log— the “Permission denied” may come from reading the file, not from your pattern.