awk
How to Install
Claude Code:
git clone --depth 1 https://github.com/jcasnellie69/homelab-config.git && cp homelab-config/skills/awk ~/.claude/skills/awk -r---
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
1. **Prefer one-liners.** Multi-line awk scripts only when a one-liner becomes
unreadable.
2. **Use `-F` to set the field separator** explicitly. Never assume space.
3. **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.
4. **Use `BEGIN`/`END` blocks** for setup and summary output.
5. **Chain with pipes** when awk alone isn't the cleanest tool.
6. **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
```bash
# Print fields 1 and 3
awk '{print $1, $3}' file.txt
# Print last field
awk '{print $NF}' file.txt
# Print second-to-last
awk '{print $(NF-1)}' file.txt
# Custom output separator
awk '{print $1 "," $2}' file.txt
```
### Filter lines
```bash
# Lines where field 3 > 100
awk '$3 > 100' file.txt
# Lines matching a regex
awk '/error/' file.txt
# Lines where field 2 matches a pattern
awk '$2 ~ /timeout/' file.txt
# Negate match
awk '$2 !~ /debug/' file.txt
# Multiple conditions
awk '$1 == "GET" && $9 >= 500' access.log
```
### Aggregation
```bash
# Sum a column
awk '{sum += $3} END {print sum}' file.txt
# Count matching lines
awk '/error/ {count++} END {print count}' file.txt
# Average
awk '{sum += $2; n++} END {print sum/n}' file.txt
# Min / max
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
```bash
# Count occurrences of each value in field 1
awk '{count[$1]++} END {for (k in count) print count[k], k}' file.txt | sort -rn
# Top 10 IPs in nginx log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# Pure awk alternative:
awk '{c[$1]++} END {for (ip in c) print c[ip], ip}' access.log | sort -rn | head -10
```
### Field reformat / transform
```bash
# Swap fields 1 and 2
awk '{print $2, $1}' file.txt
# Prepend a string to field 1
awk '{$1 = "prefix_" $1; print}' file.txt
# Change field separator in output (OFS)
awk 'BEGIN{OFS=","} {print $1,$2,$3}' file.txt
# Remove a field (shift left from field 3)
awk '{out=""; for (i=1;i<=NF;i++) if (i!=2) out = out (out?OFS:"") $i; print out}' file.txt
```
### Line ranges
```bash
# Print lines 5 to 15
awk 'NR>=5 && NR<=15' file.txt
# Print between two patterns (inclusive)
awk '/START/,/END/' file.txt
# Skip the header line
awk 'NR > 1' file.txt
# Print only the header
awk 'NR == 1' file.txt
```
### CSV / TSV processing
```bash
# TSV: print columns 1 and 4
awk -F'\t' '{print $1, $4}' data.tsv
# CSV (simple, no quoted commas): sum column 3
awk -F',' 'NR>1 {sum+=$3} END {print sum}' data.csv
# Add a computed column to CSV
awk -F',' 'BEGIN{OFS=","} NR>1 {$4=$2*$3; print}' data.csv
# Filter CSV rows where column 2 > 50
awk -F',' '$2 > 50' data.csv
# Convert TSV to 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
```bash
# Extract HTTP 5xx lines from nginx access log
awk '$9 >= 500 && $9 < 600' access.log
# Sum bytes transferred (field 10) per status code (field 9)
awk '{bytes[$9]+=$10} END {for (s in bytes) print s, bytes[s]}' access.log | sort
# Requests per minute (timestamp in field 4, format [DD/Mon/YYYY:HH:MM:SS])
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
# Strip log prefix, print only message
awk '{$1=$2=$3=""; print substr($0,4)}' app.log
```
### Multi-file processing
```bash
# Print filename alongside each line
awk '{print FILENAME, $0}' *.log
# Process file 1 as a lookup table, file 2 as data
awk 'NR==FNR {map[$1]=$2; next} {print $0, map[$1]}' lookup.txt data.txt
```
### In-place editing
```bash
# Portable awk has no in-place edit; prefer a temp file
awk '{gsub(/foo/, "bar"); print}' file.txt > file.tmp && mv file.tmp file.txt
# gawk-only in-place edit
gawk -i inplace '{gsub(/foo/, "bar"); print}' file.txt
# All .rb files
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
```bash
# OFMT: control float output precision
awk 'BEGIN{OFMT="%.2f"} {print $1 * 1.15}' prices.txt
# Pipe to a command per line
awk '{print $0 | "mail -s alert [email protected]"}' alerts.txt
# Time functions
awk 'BEGIN{print strftime("%Y-%m-%d", systime())}'
# Two-way pipe (gawk only): close the writer before reading from sort
awk 'BEGIN{cmd="sort"} {print $0 |& cmd} END{close(cmd, "to"); while ((cmd |& getline line) > 0) print line}' file.txt
# Built-in CSV parsing (gawk 5.3+)
gawk --csv '{print $2}' data.csv
# Legacy/simple CSV only: FPAT misses empty fields and escaped quotes
awk 'BEGIN{FPAT="([^,]+)|(\"[^\"]+\")"} {print $2}' data.csv
```
---
## Combining with Other Tools
```bash
# awk + sort: frequency table
awk '{print $1}' file.txt | sort | uniq -c | sort -rn
# awk + sed: awk extracts, sed cleans
awk '/error/ {print $5}' app.log | sed 's/[^a-z0-9]//g'
# awk + xargs: act on extracted values
awk '$3 > 1000 {print $1}' jobs.txt | xargs kill
# awk as a replacement for cut + grep pipeline
# Instead of: grep 'GET' access.log | cut -d' ' -f7
awk '$6 ~ /GET/ {print $7}' access.log
```
---
## Output Format
When responding to an awk request:
1. **Show the one-liner** in a code block, ready to copy-paste.
2. Add a **one-line comment** per non-obvious part.
3. If the task fits a multi-step pipeline better, show that pipeline.
4. 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.
Details
| Category | Data → data_proc |
| Source | jcasnellie69/homelab-config |
| SKILL.md | View on GitHub → |
| Repo Stars | N/A |
| Est. per Skill | N/A (shared across 48 skills from this repo) |
| Difficulty | Intermediate |
| Risk Level | Safe |
Related Skills
monte-carlo-prevent
Monte Carlo Prevent Skill This skill brings Monte Carlo's data observability context directly into y
sql-pro
You are an expert SQL specialist mastering modern database systems, performance optimization, and ad
postgresql-optimization
PostgreSQL Optimization Workflow Overview Specialized workflow for PostgreSQL database optimization
fp-data-transforms
Practical Data Transformations This skill covers the data transformations you do every day: working
Works Well With
Skills from the same repository — often designed to work together
flow
--- name: flow description: Use when you need to run Flow type checking, or when seeing Flow type er
flags
--- name: flags description: Use when you need to check feature flag states, compare channels, or de
merge
--- name: merge description: Merge changes from the topic branch to the merge base branch. Use when