sed vs awk vs grep: when to reach for which

Every terminal tutorial teaches these three with the same tired example: cat file | grep foo.
That's not a real task, it's a syntax demo.
On an actual VPS, the question isn't "how does grep work," it's "which of these three do I even use?" Here's how to answer that in about three seconds, using a real access.log.
Say your box just had a slow morning and you're digging through last night's nginx access log:
203.0.113.14 - - [18/Aug/2026:03:12:01 +0000] "GET /wp-login.php HTTP/1.1" 404 162 0.002
198.51.100.7 - - [18/Aug/2026:03:14:22 +0000] "GET /api/v1/status HTTP/1.1" 200 48 0.014
198.51.100.7 - - [18/Aug/2026:03:14:23 +0000] "GET /api/v1/status HTTP/1.1" 200 48 0.011
203.0.113.14 - - [18/Aug/2026:03:15:40 +0000] "POST /xmlrpc.php HTTP/1.1" 404 162 0.002
192.0.2.55 - - [18/Aug/2026:03:20:10 +0000] "GET /api/v1/status HTTP/1.1" 200 48 3.821
192.0.2.55 - - [18/Aug/2026:03:20:15 +0000] "GET /api/v1/report?range=30d HTTP/1.1" 200 118402 1.204
198.51.100.7 - - [18/Aug/2026:03:22:01 +0000] "GET /api/v1/status HTTP/1.1" 200 48 0.009
203.0.113.14 - - [18/Aug/2026:03:23:55 +0000] "GET /.env HTTP/1.1" 404 162 0.001
192.0.2.55 - - [18/Aug/2026:03:25:30 +0000] "GET /api/v1/report?range=7d HTTP/1.1" 200 41200 0.412
The last column is response time in seconds, added by a custom log format. That's the line that pays off later.
Question one: am I looking for lines, or looking at data inside them?
If the answer is "I just need to find the lines," that's grep. Nothing else. You want every request that hit your status endpoint:
$ grep '/api/v1/status' access.log
198.51.100.7 - - [18/Aug/2026:03:14:22 +0000] "GET /api/v1/status HTTP/1.1" 200 48 0.014
198.51.100.7 - - [18/Aug/2026:03:14:23 +0000] "GET /api/v1/status HTTP/1.1" 200 48 0.011
192.0.2.55 - - [18/Aug/2026:03:20:10 +0000] "GET /api/v1/status HTTP/1.1" 200 48 3.821
198.51.100.7 - - [18/Aug/2026:03:22:01 +0000] "GET /api/v1/status HTTP/1.1" 200 48 0.009
Four lines out of ten, full text, unmodified. That's the whole job of grep: a filter, not a processor. The moment you catch yourself piping grep's output into cut or awk '{print $1}' to pull a field back out, stop, you skipped a step. You didn't need lines, you needed data.

Question two: do I need to compute something across fields?
That 3.821-second response on line five is the actual problem, one request from 192.0.2.55 that took four thousand times longer than the others. Finding that by eye in a real log with fifty thousand lines isn't happening. This is awk's job, because awk thinks in fields and running totals, not just matched lines:
$ awk '{ bytes[$1]+=$10; if ($NF+0 > slow[$1]) slow[$1]=$NF }
END { for (ip in bytes) printf "%-15s bytes=%-8d slowest=%.3fs\n", ip, bytes[ip], slow[ip] }' access.log
198.51.100.7 bytes=144 slowest=0.014s
192.0.2.55 bytes=159650 slowest=3.821s
203.0.113.14 bytes=648 slowest=0.002s
One pass over the file, running two accumulators keyed by IP, and the answer falls out: 192.0.2.55 is both your heaviest bandwidth consumer and the source of that slow request. grep could never have told you that.
It doesn't do arithmetic, and it doesn't remember anything between lines. awk does both by default.
That's the actual dividing line between them: grep answers "which," awk answers "how much," "how many," or "what's the total."

Question three: do I need to change the file itself?
Neither grep nor awk touches your file; they both just read it and print to stdout. If the task is "the box just got renamed, and I need to update the nginx config to match," that's sed, because sed's whole reason to exist is in-place transformation:
$ cat nginx-site.conf
server {
listen 80;
server_name oldbox.example.net www.oldbox.example.net;
...
proxy_set_header Host oldbox.example.net;
}
$ sed -i.bak 's/oldbox\.example\.net/newbox.example.net/g' nginx-site.conf
$ cat nginx-site.conf
server {
listen 80;
server_name newbox.example.net www.newbox.example.net;
...
proxy_set_header Host newbox.example.net;
}
Three occurrences, one command, and the .bak suffix means the original is still sitting right next to it if the regex was wrong. (It wasn't, but check anyway, every time, no exceptions.)

The actual decision tree
Not three bullet points with bold headers, just the real question in your head when you open a log file: do you need to find lines, compute across fields, or edit the file in place?
- Find lines, grep.
- Compute or reshape data, awk.
- Change the file, sed.
Most real admin tasks are one of these, cleanly, and the rare ones that feel like two at once (find matching lines and count them) are just awk wearing grep's job for a minute, since awk can filter with a pattern before its action block just fine.
You'll still combine grep and awk plenty, and that's fine. grep filters the lines first, so awk only has to work through what's left, which matters on a big file. The real skill isn't picking a favorite tool; it's noticing what you're actually asking for: a field, a total, a changed line. Once you can name that, the right tool is obvious. Most tutorials never make you ask a real question, so you never get the practice.
“Technology is best when it brings people together.” – Matt Mullenweg



