Grep on Linux
grep is a command-line search tool in the Linux command set that scans files line by line, compares each line against a pattern, and prints the matching lines.

The linux grep command ships with every mainstream distribution, including RHEL 9 and Ubuntu 24.04 LTS, and the GNU grep 3.11 manual documents 17 options, 42 pattern characters and 6 output formats that a sysadmin uses in daily log work.
Before the flags, note where grep sits in the toolchain. GNU grep is a userspace program that the Linux Kernel exposes through the standard process and file APIs, so it runs the same on a 2 node web cluster as on a 32 core database host. That is why the patterns you learn today, from simple word matching to regular expressions, carry across every shell, every cron script and every log pipeline you will touch, and why it pairs with other core Linux Commands such as awk and sed in the classic one-liners of the sysadmin craft.
The 5 core flags every sysadmin runs
These 5 flags cover 90 percent of log searching, and each one changes how grep reads input, what it matches or what it prints. The table lists the flag, its job and the exact one-liner a server admin types at the prompt.
| Flag | Job | Example one-liner |
|---|---|---|
| -r | search a directory recursively | grep -r "Failed password" /var/log/ |
| -n | print the line number of each match | grep -n "ERROR" /var/log/syslog |
| -i | ignore case in the pattern | grep -i "timeout" /var/log/nginx/error.log |
| -c | count matching lines instead of printing them | grep -c "404" /var/log/nginx/access.log |
| -v | print only the lines that do not match | grep -v "^#" /etc/hosts |
Read the -r row with care: it follows symbolic links only with a second -r, and it skips binary files silently unless you pass -a, so a rotated log archive will not flood your terminal. The -n flag is the one you reach for first in an incident, because a 12 line excerpt without line numbers tells the pager nothing about where the failure started, while a match at line 8432 points straight at the 02:14 UTC burst. Finally, -c turns grep into a counter, which is how you turn "how often does this happen" into a single number you can put in an incident report or a cron alert.
Patterns: from fixed strings to regular expressions
Pattern strength decides how precisely grep matches, and the default basic regular expression mode is the right starting point for log work. A fixed string like 404 matches any line containing that sequence, while the pattern 404 [0-9]\{3\} matches the status code only when it is followed by a space and a 3 digit number, which is exactly how nginx writes the response time field in the 1.24 access log format. The -F flag switches grep to fixed-string matching, which skips pattern compilation entirely and makes it roughly 3 to 5 times faster on long strings, the kind you copy straight from a stack trace or a 64 character API key fragment. For timestamps, the bracket expression [0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\} anchors a full date, and the alternation operator, written as \(...\)\|(...) in basic mode, lets one line catch both ERROR and CRITICAL in a single pass over a 2 GB journal.
Two habits keep these patterns honest. Test every pattern against a small 20 line sample file before running it across /var/log, and quote the pattern so the shell does not interpret the asterisks and brackets before grep ever sees them. The anchor symbols, the caret at the start of the line and the dollar sign at the end, are the cheapest precision available: grep -c "^5[0-9][0-9] " turns a noisy access log into a clean count of server errors.
One-liners for log files that actually get typed
Real log work is rarely a single grep; it is a pipeline of 2 to 4 commands, each with one job. The first one-liner below counts failed logins per source IP and sorts the results so the loudest attacker is on line 1, the second pulls every 5 minute window around a failure, and the third joins the match with the line that follows it for context.
- Count the top 10 offenders: grep "Failed password" /var/log/auth.log | grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}" | sort | uniq -c | sort -rn | head -10
- Grab 10 lines before and 10 lines after each OOM kill: grep -B 10 -A 10 "Out of memory" /var/log/syslog
- Pair each error with the request line above it: grep -B 2 "500 Internal" /var/log/nginx/error.log
- Strip timestamps to compare two runs: grep "ERROR" /var/log/app.log | cut -d" " -f4- | sort | uniq -c
The -B and -A flags take a line count, so -B 10 -A 10 prints a 21 line window around each match, which is enough to see the request that preceded the crash without dumping the whole file. The -o flag prints only the matched part of the line, which is what makes the IP extraction in the first item work: grep hands sort a column of bare addresses, uniq collapses duplicates, and the -rn flag on sort reverses the numeric order so the highest count comes first.
From grep to jq: searching structured data
grep searches lines, but jq searches fields, so the right tool flips the moment the log stops being plain text. Modern stacks increasingly write JSON lines, one object per line, and there grep becomes blunt: you can match the string "status":500, but you cannot ask which of those 500 responses came from the payments endpoint. jq was released in 2013, and a single call, jq '.status' /var/log/app.json, returns the status field of every object as one number per line. The query jq 'select(.level=="error")' does exactly what grep -i "error" does for plain text, but it reads the field named level instead of a character sequence, which means a line containing the word error in a user message will not false-positive. For the count, jq -s '[.[] | select(.latency > 500)] | length' answers "how many requests exceeded 500 ms" over a whole file in one pass, and the same question in grep is three commands and a prayer that the number sits where you expect. When both tools are available, the working rule is simple: plain text goes to grep, one object per line goes to jq, and the two share the same pipeline, since jq can read from a pipe the way grep does.
Grep versus history: two search histories, two different jobs
Both grep and the history command search the past, but they search different things, and mixing them up wastes minutes in an incident. The history command lists the lines of your shell history file, usually 1000 entries in bash, and you can filter it with the same tool this page is about: history | grep -i "systemctl restart" finds every restart you typed in the last session and the exact order they ran in. That search lives in memory and in a single user's dot file, and it is gone the moment the session ends unless you keep HISTSIZE higher. grep, by contrast, searches files on disk that outlive the session: the journal entries, the rotated logs, the audit trail. When a service failed at 03:00, history tells you what you typed, grep tells you what the machine recorded, and the incident write-up needs both. The Linux Commands you will reach for in that sequence, history, grep and tail, are the whole triad: recall, search, follow.
History and a modern testbed
GNU grep descends from the BSD grep that appeared in the early 1970s, and the 3 main lineages, BSD, GNU and BusyBox, still ship side by side today. The GNU version entered coreutils-adjacent packaging as its own project in 1992, and version 3.11, released in 2025, added the --threads option that splits a large directory search across 4 worker threads on a 4 core box. On a minimal appliance, BusyBox grep provides the same -r -n -i -c -v set in a fraction of the binary size, which is why the flags you learn on a big server also work inside a 50 MB container image. If you want a safe place to practice every pattern on this page without touching production logs, a Linux Mint live session gives you a full desktop with the terminal, a preinstalled journal and the complete GNU toolset in one 2 GB download, and the same commands you type there run unchanged on the server. The 30 minutes you spend rebuilding the one-liners above in that sandbox are the difference between reading about grep and running it with confidence.