sed/awk/grep Cheatsheet - Linux Text Processing Reference
Log triage and report statistics are almost always three stages: grep to keep the lines that matter, sed to rewrite the values that need changing, awk to aggregate by column. The sheet runs along those three paths, then widens into pipeline recipes, sort/uniq/comm for ordering and set comparison, and cut/tr/paste for column surgery. The choice between them is granularity rather than preference: grep filters whole lines, sed rewrites each line in place, and awk is the only one that can accumulate, group-count and emit sorted results per column. Pick the wrong one and the cost is obvious — counting occurrences per IP is one awk line, while grep plus wc -l means running the pipeline once per key. The pipeline section supplies the joins between stages, including filter-then-aggregate and dedupe-with-count. Two operational details carry weight: add --line-buffered to grep on large inputs, and remember $1 is safe inside single quotes in awk but must be escaped inside double quotes.
Typical Use Case
For developers doing log triage and report aggregation: grep to locate, sed to rewrite, awk to pull columns and total, and sort/uniq to count and deduplicate. Piped together these four cover most day-to-day text processing. The key is keeping their roles distinct — grep filters lines, sed rewrites or deletes lines, awk extracts columns and computes. Mixing them up (using grep to pull a column, say) is the most common source of wasted time for newcomers. This sheet is organised as five ready-made chains, usable directly as a troubleshooting reference.
grep Match & Search 10
grep "error" app.loggrep -i "error" app.loggrep -rn "TODO" src/grep -v "debug" app.loggrep -c "error" app.loggrep -E "error|warning" app.loggrep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.loggrep -A 3 "error" app.loggrep -B 2 "error" app.loggrep -f pattern.txt app.logsed Replace & Edit 9
sed 's/old/new/g' file.txtsed -i 's/old/new/g' file.txtsed -i.bak 's/old/new/g' file.txtsed '5d' file.txtsed '/^#/d' file.confsed '/^$/d' file.txtsed -n '10,20p' file.txtsed 's/[[:space:]]*$//' file.txtsed -E 's/([0-9]+)/\1/' file.txtawk Extract & Summarize 9
awk '{print $1}' access.logawk -F: '{print $1, $3}' /etc/passwdawk 'NR==10' file.txtawk 'NR>=10 && NR<=20' file.txtawk '{sum += $1} END {print sum}' nums.txtawk '{count[$1]++} END {for(k in count) print k, count[k]}' access.logawk '$3 > 100' data.txtawk 'length > 80' file.txtawk -F: '$3 >= 1000 {print $1}' /etc/passwdPipelines 4
grep "error" app.log | awk "{print \$1}" | sort | uniq -c | sort -rn | headcat access.log | awk "{print \$1}" | sort | uniq -c | sort -rn | head -20grep -oE "[0-9]+ms" app.log | sort -rn | head -10sed '/^#/d; /^$/d' nginx.confsort / uniq / comm 10
sort file.txtsort -n file.txtsort -r file.txtsort -t: -k3 -n /etc/passwdsort -u file.txtsort file.txt | uniq -c | sort -rn | headuniq -c file.txtuniq -d file.txtcomm file1 file2diff -u file1 file2cut / tr / paste 10
cut -d: -f1 /etc/passwdcut -c1-10 file.txtcut -f1,3 file.txttr 'a-z' 'A-Z' < file.txttr -d ' \t' < file.txttr -s '\n' < file.txtpaste file1.txt file2.txtcolumn -t file.txtexpand file.txtcommand | tee output.txtParameter matrix
| Parameter | Effect | Example |
|---|---|---|
grep -r / -R | Search directories recursively; the capital -R additionally follows symbolic links. | grep -rn "TODO" src/ |
grep -i / -v | Ignore case when matching, or invert the test to keep only non-matching lines. | grep -iv "debug" app.log |
grep -C <n> | Print n lines of context on both sides of every match. | grep -C 3 "error" app.log |
grep -o | Print only the matching part of the line instead of the whole line. | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' access.log |
sed -i.bak | Edit the file in place while keeping a .bak copy of the original. | sed -i.bak 's/old/new/g' f.txt |
sed 's/a/b/g' | Substitute globally. Without the g flag only the first occurrence per line is replaced. | sed 's/localhost/0.0.0.0/g' c.yaml |
sed '/pat/d' | Delete every line matching the pattern. | sed '/^#/d' nginx.conf |
awk -F<sep> | Set the field separator, since awk splits on runs of whitespace by default. | awk -F: '{print $1, $3}' /etc/passwd |
awk $n / $NF | Reference the nth field, or $NF for the last field of the record. | awk '{print $1, $NF}' f.txt |
awk NR / NF | NR is the current line number and NF the number of fields on that line. | awk 'NR>=10 && NR<=20' f.txt |
sort | uniq -c | sort -rn | Count duplicates and order the result numerically, highest first. | awk '{print $1}' access.log | sort | uniq -c | sort -rn |
cut -d -f | Pull fixed columns out of each line by delimiter. | cut -d: -f1 /etc/passwd |
Common pitfalls
SymptomAfter uniq the duplicates are still there and the deduplication looks broken.
Causeuniq only compares adjacent lines, and nothing sorted the input so identical lines are scattered.
FixAlways write sort file | uniq -c, or use sort -u which does both in one step.
SymptomYou wanted to undo a sed edit, but the file on disk is already changed.
Causesed -i rewrote the file in place and no backup was kept.
FixUse sed -i.bak so a .bak file survives, or run without -i first and inspect the output before writing.
Symptomgrep fails to find a string you can plainly see in the file.
CauseThe case differs, or a metacharacter in your pattern was interpreted as a regex.
FixAdd -i, and use -F for literal search (a plain pattern would also match axb when looking for a.b).
SymptomEvery column awk prints comes back empty.
CauseWrong separator: the file is colon-delimited but -F: was omitted, so awk split on whitespace and the whole line became one field.
FixPass the real separator with awk -F: and inspect a few lines with head before writing the expression.
SymptomThe IP address counts are wrong because every line is counted as one entry.
CauseThe extraction step piped into sort and uniq was missing, or grep -o was omitted so uniq counted whole lines.
FixRun grep -oE "pattern" file | sort | uniq -c | sort -rn; the -o flag is what makes this correct.
SymptomA sed substitution emptied the file.
CauseThe / delimiter collided with a path inside the pattern, or the d command was used where s was intended.
FixSwitch to # as the delimiter when the text contains /, and preview without -i before applying.
Troubleshooting
1Look at the raw data before writing any expression
head -3 access.logConfirming the separator and column positions up front prevents most failed awk one-liners.
2Get the top 20 entries by access volume
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20Extract the column, sort so identical values become adjacent, count, then order numerically: all four steps are required.
3Locate an error with surrounding context
grep -n -C 3 "FATAL" app.log-n prints line numbers and -C 3 adds three lines either side, which is usually all the evidence you need.
4Dry-run an edit before touching the file
sed 's/old/new/g' config.yamlWithout -i it only prints; once the output looks right, apply it with sed -i.bak.
5Strip comments and blank lines in a single pass
sed '/^#/d; /^$/d' nginx.confSeparate the commands with semicolons and one scan does the whole job.
Command Examples
Top 20 IPs by request count
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20awk 取第一列,sort 让相同值相邻,uniq -c 计数,sort -rn 按数值降序——四步缺一不可,直接 uniq 会因为行不相邻而失效。
Output
1842 203.0.113.7
931 198.51.100.22
407 192.0.2.180Extract URLs that returned 5xx
awk '$9 ~ /^5/ {print $7}' access.log | sort | uniq -c | sort -rn标准 combined 日志格式里第 9 列是状态码、第 7 列是请求路径,所以条件直接落在第 9 列上。比 grep 整行后再切分快得多。
Output
87 /api/orders
34 /api/checkout
12 /api/loginStrip comment and blank lines from a config
sed '/^#/d; /^$/d' nginx.confsed 的多条命令用分号分隔,可以在一次扫描里完成。配合 -i.bak 就能原地改文件且留有备份。
Output
worker_processes auto;
events {
worker_connections 1024;
}Dry-run a substitution before writing to disk
sed 's#http://#https://#g' config.yaml先把 / 换成 # 作分隔符,避免 URL 里的斜杠与 sed 的分隔符冲突。不加 -i 只输出不改文件,确认无误后再加 -i.bak 落盘。
Common Pitfalls
- sed -i edits in place with no backup and no undo; verify output without -i first, then use -i.bak.
- sed defaults to BRE, where \+ \| and \( are not special; add -r for extended regular expressions.
- grepping a URL path containing / collides with the delimiter — switch to another delimiter such as #.
- uniq only compares adjacent lines; without a preceding sort it silently misses duplicates scattered through the file.
- awk fields start at 1, unlike array indices: $0 is the whole line and $NF is the last field.
- cut does not understand multi-character delimiters and computes nothing; use awk for complex separators or aggregation.
Tips
- Back up before sed -i edits a file (-i.bak), or preview without -i first.
- For large files, add --line-buffered so grep flushes output live; otherwise the pipe waits for the buffer to fill.
- In awk, $1 inside single quotes gets the column; inside double quotes escape it as \$1 or the shell will expand it.
FAQ
How should grep, sed and awk be combined when debugging logs?
The usual chain is filter, then edit, then aggregate: use grep -i "error" app.log to narrow to the lines you care about, use sed rules such as '/^#/d' to strip noise, then use awk to sum by column. To find the top errors: grep "error" app.log | awk "{print $1}" | sort | uniq -c | sort -rn | head. grep locates, sed rewrites and filters by line, awk extracts columns and computes — piping them together beats writing a script.
sed -i edits in place and feels risky — how do I limit the damage?
Use sed -i.bak 's/old/new/g' file.txt so an automatic .bak copy is kept, and delete it once you are satisfied. Or run sed 's/old/new/g' file.txt without -i first and read the output before writing anything to disk. These two habits prevent nearly every irreversible mistake.
What do $0, $1, NR and NF mean in awk?
$0 is the whole line; $1, $2 are the columns split by the field separator ($NF is the last one); NR is the current record number; NF is the number of fields in the current line. All are built-in and need no declaration. So awk '{print NR, $1}' file prints line number plus first column, and awk 'NR>=10 && NR<=20' file processes only lines 10 through 20.
Why do sort -u and uniq need to be combined with sort?
uniq only compares adjacent lines — deduplication assumes equal values already sit next to each other. sort brings equal lines together, which is why the idiom is sort file.txt | uniq -c, or the one-step sort -u file.txt. Without sorting first, duplicates scattered through the file can never be recognized.
Both cut and awk extract columns — when should I use which?
cut -d: -f1 /etc/passwd is fastest for "one single-character delimiter, a few fixed fields" and prints clean output, but it does not understand multi-character delimiters and does no computation. awk -F: '{print $1, $3}' /etc/passwd is more flexible: multi-character separators, conditional filtering, and aggregation. Rule of thumb — plain column extraction with cut, anything involving calculation or complex delimiters with awk.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us