| name | awk |
| description | Expert GNU awk (gawk) one-liner generation and transformation. Use this skill whenever the user asks to process text fields, extract columns, compute sums or aggregates, filter structured output, reformat CSV/TSV, parse logs, generate reports, or do any record/field processing task. Trigger on keywords like "awk", "extract column", "sum a field", "count occurrences", "print field N", "filter by column", "reformat output", "aggregate log data", "process CSV", "TSV", or any request that maps to a field-oriented text transformation. When in doubt, use this skill — awk is often the right tool when sed isn't enough.
|
GNU awk (gawk) Skill
All output assumes gawk (awk --version shows GNU Awk). POSIX awk
compatibility is not a goal; gawk extensions are used freely where they help.
Core Principles
- Prefer one-liners. Multi-line awk scripts only when a one-liner becomes
unreadable.
- Use
-F to set the field separator explicitly. Never assume space.
- Dry-run friendly by default. awk reads and prints; it never edits files
in-place. Redirect explicitly (
> out && mv out in) when the user wants
in-place behavior.
- Use
BEGIN/END blocks for setup and summary output.
- Chain with pipes when awk alone isn't the cleanest tool.
- Never use awk to parse JSON or XML. Redirect to
jq / xmllint and say
why.
Mental Model
awk 'BEGIN { setup } /pattern/ { action } END { summary }' file
- awk reads input line by line (records, split by
RS, default \n).
- Each record is split into fields by
FS (default: any whitespace).
$0 = entire record. $1, $2, ... $NF = individual fields. NF = number
of fields. NR = current record number. FNR = record number within current
file.
Flags
| Flag | Meaning |
|---|
-F SEP | Set field separator (string or regex) |
-v VAR=VAL | Set a variable before execution |
-f FILE | Read awk program from a file |
--sandbox | Disable system(), getline, pipe (safe) |
Command Quick Reference
Print specific fields
awk '{print $1, $3}' file.txt
awk '{print $NF}' file.txt
awk '{print $(NF-1)}' file.txt
awk '{print $1 "," $2}' file.txt
Filter lines
awk '$3 > 100' file.txt
awk '/error/' file.txt
awk '$2 ~ /timeout/' file.txt
awk '$2 !~ /debug/' file.txt
awk '$1 == "GET" && $9 >= 500' access.log
Aggregation
awk '{sum += $3} END {print sum}' file.txt
awk '/error/ {count++} END {print count}' file.txt
awk '{sum += $2; n++} END {print sum/n}' file.txt
awk 'NR==1 || $3 < min {min=$3} END {print min}' file.txt
awk 'NR==1 || $3 > max {max=$3} END {print max}' file.txt
Frequency / histogram
awk '{count[$1]++} END {for (k in count) print count[k], k}' file.txt | sort -rn
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
awk '{c[$1]++} END {for (ip in c) print c[ip], ip}' access.log | sort -rn | head -10
Field reformat / transform
awk '{print $2, $1}' file.txt
awk '{$1 = "prefix_" $1; print}' file.txt
awk 'BEGIN{OFS=","} {print $1,$2,$3}' file.txt
awk '{out=""; for (i=1;i<=NF;i++) if (i!=2) out = out (out?OFS:"") $i; print out}' file.txt
Line ranges
awk 'NR>=5 && NR<=15' file.txt
awk '/START/,/END/' file.txt
awk 'NR > 1' file.txt
awk 'NR == 1' file.txt
CSV / TSV processing
awk -F'\t' '{print $1, $4}' data.tsv
awk -F',' 'NR>1 {sum+=$3} END {print sum}' data.csv
awk -F',' 'BEGIN{OFS=","} NR>1 {$4=$2*$3; print}' data.csv
awk -F',' '$2 > 50' data.csv
awk 'BEGIN{FS="\t"; OFS=","} {$1=$1; print}' data.tsv
For CSV with quoted fields containing commas, use gawk with a proper CSV
library or switch to miller (mlr) / csvkit.
Log processing
awk '$9 >= 500 && $9 < 600' access.log
awk '{bytes[$9]+=$10} END {for (s in bytes) print s, bytes[s]}' access.log | sort
awk '{match($4, /[0-9]{2}\/[A-Za-z]+\/[0-9]{4}:[0-9]{2}:[0-9]{2}/, m); c[m[0]]++}
END {for (t in c) print t, c[t]}' access.log | sort
awk '{$1=$2=$3=""; print substr($0,4)}' app.log
Multi-file processing
awk '{print FILENAME, $0}' *.log
awk 'NR==FNR {map[$1]=$2; next} {print $0, map[$1]}' lookup.txt data.txt
In-place editing
awk '{gsub(/foo/, "bar"); print}' file.txt > file.tmp && mv file.tmp file.txt
gawk -i inplace '{gsub(/foo/, "bar"); print}' file.txt
for f in src/**/*.rb; do
awk '{gsub(/OldClass/, "NewClass"); print}' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
done
Built-in Functions
String
| Function | Description |
|---|
length(s) | String length |
substr(s, start, len) | Substring (1-indexed) |
index(s, t) | Position of t in s (0 = not found) |
split(s, arr, sep) | Split s into arr by sep, returns count |
gsub(re, rep, s) | Global replace in s (default: $0) |
sub(re, rep, s) | First replace in s |
match(s, re, arr) | Match re in s; fills arr with groups |
sprintf(fmt, ...) | Format string (like printf) |
tolower(s) / toupper(s) | Case conversion |
gensub(re, rep, how, s) | gawk: replace with back-references (\1) |
Math
| Function | Description |
|---|
int(x) | Truncate to integer |
sqrt(x) | Square root |
log(x) / exp(x) | Natural log / e^x |
sin(x) / cos(x) | Trig (radians) |
rand() | Random [0,1) |
srand(seed) | Seed RNG |
gawk Extensions Worth Knowing
awk 'BEGIN{OFMT="%.2f"} {print $1 * 1.15}' prices.txt
awk '{print $0 | "mail -s alert ops@example.com"}' alerts.txt
awk 'BEGIN{print strftime("%Y-%m-%d", systime())}'
awk 'BEGIN{cmd="sort"} {print $0 |& cmd} END{close(cmd, "to"); while ((cmd |& getline line) > 0) print line}' file.txt
gawk --csv '{print $2}' data.csv
awk 'BEGIN{FPAT="([^,]+)|(\"[^\"]+\")"} {print $2}' data.csv
Combining with Other Tools
awk '{print $1}' file.txt | sort | uniq -c | sort -rn
awk '/error/ {print $5}' app.log | sed 's/[^a-z0-9]//g'
awk '$3 > 1000 {print $1}' jobs.txt | xargs kill
awk '$6 ~ /GET/ {print $7}' access.log
Output Format
When responding to an awk request:
- Show the one-liner in a code block, ready to copy-paste.
- Add a one-line comment per non-obvious part.
- If the task fits a multi-step pipeline better, show that pipeline.
- If the task is better served by
jq, mlr (miller), python, or perl, say
so briefly and optionally show that alternative too.
Gotchas
- Portable awk has no in-place editing. Use
> tmp && mv tmp original unless
you intentionally depend on gawk's -i inplace extension.
- Field assignment triggers rebuild of
$0 using OFS. Set OFS in
BEGIN if you rely on this.
- Arrays are associative only (hash maps). There are no integer-indexed arrays
with guaranteed order; iterate with
for (k in arr) (order is undefined). Use
a separate key list + asort()/asorti() (gawk) when order matters.
- Rebuilding vs re-splitting. Assigning to a field rebuilds
$0 from fields
using OFS ($1=$1 is the common idiom). Assigning to $0, or sub()/gsub()
on $0, re-splits fields using the current FS.
- Regex constants vs strings.
/foo/ is a regex literal; "foo" is a string.
$1 ~ /foo/ is correct; $1 ~ "foo" also works but avoids the literal syntax.
- Integer division.
5/2 returns 2.5 in awk (unlike shell). Use int(5/2)
for truncation.
- macOS ships BSD/one-true-awk, not gawk.
gawk must be installed via
Homebrew. FPAT, gensub(), strftime(), and |& are gawk-only.