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)

CallReturns
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") is 5 under gawk and 6 under macOS awk, and substr("héllo", 2, 1) returns é under gawk but half a byte pair under macOS awk. LC_ALL=C gawk reproduces 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

CallNotes
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

CallNotes
print / printfto 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
getlinesee 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.

FormSetsReads from
getline$0 NF NR FNRcurrent input
getline varvar NR FNRcurrent input
getline < file$0 NFfile
getline var < filevar onlyfile
cmd | getline$0 NF NRcmd output
cmd | getline varvar NRcmd 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==FNR two-file idiom over getline whenever you can. getline without a var clobbers $0 and NF, and the plain getline form advances NR behind the back of your other rules.

Function availability at a glance

FunctionPOSIXmacOS awkgawkmawk
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)✓✓