awk Cheatsheet
Built-in Functions
Use this awk reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
String functions (POSIX, everywhere)
| Call | Returns |
|---|---|
length(s) | characters in s, or in $0 if omitted |
substr(s, m) | from position m (1-based) to the end |
substr(s, m, n) | at most n characters from position m |
index(s, t) | 1-based position of t in s, or 0 |
split(s, a[, fs]) | fills a[1..n], returns n, clears a first |
sub(re, rep[, target]) | replaces the first match, returns 0 or 1 |
gsub(re, rep[, target]) | replaces all matches, returns the count |
match(s, re) | position of the match or 0, sets RSTART and RLENGTH |
sprintf(fmt, ...) | the formatted string, no output |
tolower(s) / toupper(s) | case-folded copy |
awk 'BEGIN{ print substr("hello", 2, 3) }' # ell awk 'BEGIN{ print index("hello", "ll") }' # 3
length with no parentheses and no argument is legal: awk 'length > 80'.
Bytes versus characters. gawk in a UTF-8 locale counts characters, macOS awk counts bytes.
length("héllo")is5under gawk and6under macOS awk, andsubstr("héllo", 2, 1)returnséunder gawk but half a byte pair under macOS awk.LC_ALL=C gawkreproduces the byte behavior.
sub and gsub modify in place
Both take the target by reference and default to $0. They return a count, not
the new string, which is the number one mistake.
# wrong: prints 1, the return value awk 'BEGIN{ s="cat"; print sub(/a/, "o", s) }' # 1 # right: modify then print awk 'BEGIN{ s="cat"; sub(/a/, "o", s); print s }' # cot # strip trailing whitespace from every line awk '{ sub(/[ \t]+$/, ""); print }' file # replace in one field only, then rebuild with OFS awk 'BEGIN{OFS=","} { gsub(/-/, "_", $2); $1=$1; print }' data.txt
& in the replacement means "the whole match". Escape it as \\& for a literal
ampersand. There are no \1 backreferences in the replacement in POSIX awk.
awk 'BEGIN{ s="cat"; sub(/a/, "[&]", s); print s }' # c[a]t awk 'BEGIN{ s="cat"; sub(/a/, "[\\&]", s); print s }' # c[&]t
For a capture-group replacement you need gawk's gensub, which returns a new
string and leaves the target alone:
# gawk only gawk 'BEGIN{ print gensub(/([a-z]+) ([a-z]+)/, "\\2 \\1", "g", "john smith") }' # smith john
Numeric and time functions
| Call | Notes |
|---|---|
int(x) | truncates toward zero, int(-1.9) is -1 |
sqrt(x) exp(x) log(x) | natural log |
sin(x) cos(x) atan2(y, x) | radians |
rand() | 0 ≤ r < 1, the same sequence every run unless seeded |
srand([x]) | seed, returns the previous seed. srand() seeds from the clock |
systime() | gawk only, current epoch seconds |
strftime([fmt[, ts]]) | gawk only |
mktime("YYYY MM DD HH MM SS") | gawk only |
# random integer 1-100, seeded properly awk 'BEGIN{ srand(); print int(rand() * 100) + 1 }' # rounding, since awk has no round() awk 'BEGIN{ x = 2.5; print int(x + 0.5) }' # 3 awk 'BEGIN{ printf "%.0f\n", 2.5 }' # 2, banker's rounding # gawk only: epoch to date gawk 'BEGIN{ print strftime("%Y-%m-%d %H:%M", 1700000000) }'
On macOS awk there is no systime, so shell out instead:
awk -v now="$(date +%s)" 'BEGIN{ print now }' awk 'BEGIN{ "date +%F" | getline today; close("date +%F"); print today }'
I/O functions
| Call | Notes |
|---|---|
print / printf | to stdout, or > "file", >> "file", | "cmd" |
close(expr) | close a file or pipe, returns 0 on success |
fflush([expr]) | flush, fflush() with no argument flushes everything |
system(cmd) | run through the shell, returns the exit status |
getline | see below |
# split one file into many, closing as you go or you hit the fd limit awk '{ f = "out_" $1 ".txt"; print > f } END { for (k in seen) close(k) }' data.txt # interleave with a shell command, flush first or ordering scrambles awk '{ print "awk:", $0; fflush(); system("echo shell: " $1) }' data.txt
- system() and a pipe both go through /bin/sh, so quote your data.
- awk keeps every redirected file and pipe open until you close() it. Long runs
that open a file per key will exhaust file descriptors.
- close() on a pipe returns the command's exit status in gawk. macOS awk returns
0 regardless, so do not branch on it portably.
getline, and why it is risky
getline reads the next record from somewhere. Six forms, with different side
effects on $0, NF, NR, and FNR.
| Form | Sets | Reads from |
|---|---|---|
getline | $0 NF NR FNR | current input |
getline var | var NR FNR | current input |
getline < file | $0 NF | file |
getline var < file | var only | file |
cmd | getline | $0 NF NR | cmd output |
cmd | getline var | var NR | cmd output |
Return value: 1 on a record, 0 at end of input, -1 on an error. Always test
it, and always test > 0, because while (getline line < f) loops forever on a
missing file.
# read a whole side file into an array awk 'BEGIN{ while ((getline line < "keys.txt") > 0) k[line]=1 close("keys.txt") } $1 in k' data.txt # capture one command's output awk 'BEGIN{ "uname -s" | getline os; close("uname -s"); print "os:", os }' # merge the next line into this one awk '/continues$/ { if ((getline nxt) > 0) $0 = $0 " " nxt } { print }' file
Prefer the
NR==FNRtwo-file idiom overgetlinewhenever you can.getlinewithout avarclobbers$0andNF, and the plaingetlineform advancesNRbehind the back of your other rules.
Function availability at a glance
| Function | POSIX | macOS awk | gawk | mawk |
|---|---|---|---|---|
length substr index split | ✓ | ✓ | ✓ | ✓ |
sub gsub match sprintf | ✓ | ✓ | ✓ | ✓ |
tolower toupper system close | ✓ | ✓ | ✓ | ✓ |
fflush | ✓ | ✓ | ✓ | ✓ |
length(array) | ✗ | ✓ | ✓ | ✓ |
systime strftime mktime | ✗ | ✗ | ✓ | ✓ |
gensub patsplit | ✗ | ✗ | ✓ | ✗ |
asort asorti | ✗ | ✗ | ✓ | ✗ |
character-aware length / substr | ✗ | ✗ (bytes) | ✓ | ✗ (bytes) |
exit status from close(pipe) | ✗ | ✗ (always 0) | ✓ | ✓ |