Beginner

Control Flow in AWK

Master control flow in AWK with patterns, if/else, for loops, while loops, switch statements, and loop control

Control flow in AWK is unique among programming languages. While AWK includes the familiar if/else, for, and while constructs from C, its primary control flow mechanism is something else entirely: pattern-action rules. The order in which patterns match input records, and which actions execute as a result, is itself a form of control flow that has no direct equivalent in most languages.

In AWK, every program runs inside an implicit main loop that reads input one record at a time and tests each rule against that record. This means you rarely need explicit loops to walk through data — the language does it for you. When you do need imperative control flow inside an action block, AWK provides a complete C-like set of structured statements.

This tutorial covers both halves of AWK’s control flow: the pattern-driven outer loop, and the imperative constructs available inside actions.

Pattern-Action Rules as Control Flow

In AWK, the outermost form of control flow is the pattern. Each rule’s pattern is a condition that decides whether its action runs for the current input record. Patterns can be ranges, regular expressions, boolean expressions over fields, or the special BEGIN and END patterns.

Create a file named patterns.awk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Pattern-action rules as control flow
BEGIN {
    FS = ","
    print "=== Order Report ==="
}

# Skip the header line
NR == 1 { next }

# Range pattern: rules from the first "START" line to the first "STOP" line
/START/,/STOP/ { in_section = 1 }

# Regex pattern: lines containing "urgent"
/urgent/ { urgent_count++ }

# Expression pattern: numeric comparison on a field
$3+0 > 100 { big_orders++; big_total += $3 }

# Compound pattern: both conditions must hold
$2 == "ship" && $3+0 > 50 { ship_value += $3 }

END {
    print "Big orders (>100):", big_orders, "total:", big_total
    print "Shippable value (>50):", ship_value
    print "Urgent mentions:", urgent_count+0
}

Create a file named orders.csv:

id,action,amount,note
1,ship,150,routine
2,hold,40,urgent review
3,ship,75,START
4,ship,200,urgent
5,hold,30,STOP
6,ship,500,routine

Each rule’s pattern decides — independently of the others — whether its action fires for the current record. Multiple rules can fire on the same record. This declarative style replaces a large amount of imperative branching.

Conditionals: if, else if, else, and the Ternary Operator

Inside an action block, AWK supports C-style conditionals. Braces are optional for single statements but recommended for clarity.

Create a file named conditionals.awk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Classify field $3 (amount) into buckets
BEGIN { FS = "," }
NR == 1 { next }
{
    amount = $3 + 0

    if (amount >= 200) {
        bucket = "huge"
    } else if (amount >= 100) {
        bucket = "large"
    } else if (amount >= 50) {
        bucket = "medium"
    } else {
        bucket = "small"
    }

    # Ternary expression
    flag = (amount > 100) ? "*" : " "

    printf "%s id=%s amount=%-4d bucket=%s\n", flag, $1, amount, bucket
}

AWK has no elif keyword — chains use else if. The ternary operator cond ? a : b works exactly as in C and is idiomatic for short conditional values.

Loops: for, while, and do-while

When the implicit per-record loop is not enough, AWK provides three explicit loops. The C-style for is the most common and is used both for counting and for iterating fields with NF.

Create a file named loops.awk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BEGIN { FS = "," }
NR == 1 { next }
{
    # C-style for: walk every field on this record
    for (i = 1; i <= NF; i++) {
        printf "  field %d: %s\n", i, $i
    }

    # while loop: countdown using the amount field
    n = $3 + 0
    while (n > 0 && n >= 100) {
        n -= 50
    }
    printf "  remainder of %s after draining: %d\n", $3, n

    # do-while: runs the body at least once
    tries = 0
    do {
        tries++
    } while (tries < 2)
    printf "  tries=%d\n", tries

    print "---"
}

The do { ... } while (cond) form is useful when the body must run at least once before the condition is evaluated. All three loops share the same break and continue keywords described below.

The for-in Loop over Associative Arrays

AWK arrays are associative, so the for (key in array) form is the natural way to iterate them. Iteration order is implementation-defined — do not rely on insertion or sorted order without explicitly sorting.

Create a file named tally.awk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
BEGIN { FS = "," }
NR == 1 { next }
{
    # Tally counts and sums per action type
    count[$2]++
    total[$2] += $3
}
END {
    # for-in iterates the keys of the array
    for (action in count) {
        printf "%-5s n=%d sum=%d\n", action, count[action], total[action]
    }
}

Because for (key in array) has no guaranteed order, the output lines for ship and hold may appear in either order depending on the AWK implementation.

switch, next, break, and continue

GNU AWK adds a switch statement; not all AWKs support it, so for portability many programs use chained if/else. The next statement is AWK-specific and immediately advances to the next input record, skipping any remaining rules — a powerful way to express “I am done with this record.”

Create a file named flow_control.awk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
BEGIN { FS = "," }

# Skip the header without falling through to later rules
NR == 1 { next }

{
    # Use break to exit a loop early
    for (i = 1; i <= NF; i++) {
        if ($i == "STOP") {
            stops++
            break
        }
    }

    # Use continue to skip the current iteration
    for (i = 1; i <= NF; i++) {
        if ($i == "")
            continue
        nonempty++
    }

    # switch (GNU AWK extension) — fall back to if/else for portability
    action = $2
    if (action == "ship") {
        shipped++
    } else if (action == "hold") {
        held++
    } else {
        other++
    }
}

END {
    print "stops:", stops+0
    print "nonempty fields:", nonempty
    print "shipped:", shipped, "held:", held, "other:", other+0
}

next is one of the most distinctive AWK control flow statements: it returns control to the implicit main loop, advancing to the next record without checking any further patterns for the current one. There is also nextfile, which jumps to the next input file — useful when processing multiple files.

Running with Docker

1
2
3
4
5
6
7
8
9
# Pull the Docker image
docker pull alpine:latest

# Run each example against orders.csv
docker run --rm -v $(pwd):/app -w /app alpine:latest awk -f patterns.awk orders.csv
docker run --rm -v $(pwd):/app -w /app alpine:latest awk -f conditionals.awk orders.csv
docker run --rm -v $(pwd):/app -w /app alpine:latest awk -f loops.awk orders.csv
docker run --rm -v $(pwd):/app -w /app alpine:latest awk -f tally.awk orders.csv
docker run --rm -v $(pwd):/app -w /app alpine:latest awk -f flow_control.awk orders.csv

Alpine ships BusyBox AWK, which supports all standard control flow constructs used above. For the switch statement specifically, swap in gawk if needed.

Expected Output

Output from patterns.awk:

=== Order Report ===
Big orders (>100): 3 total: 850
Shippable value (>50): 925
Urgent mentions: 2

Output from conditionals.awk:

* id=1 amount=150  bucket=large
  id=2 amount=40   bucket=small
  id=3 amount=75   bucket=medium
* id=4 amount=200  bucket=huge
  id=5 amount=30   bucket=small
* id=6 amount=500  bucket=huge

Output from flow_control.awk:

stops: 1
nonempty fields: 24
shipped: 4 held: 2 other: 0

Key Concepts

  • Patterns are control flow. Each rule’s pattern decides whether its action fires; AWK’s outer loop is implicit and walks every input record automatically.
  • BEGIN and END are the bookends for setup and teardown; they always run exactly once and have no input record while executing.
  • next skips remaining rules for the current record and advances the implicit loop — there is no equivalent in C-family languages.
  • if/else if/else and the ternary ?: cover conditional branching inside actions; there is no elif keyword.
  • for, while, and do-while are C-style loops; for (key in array) iterates associative arrays in implementation-defined order.
  • break and continue behave exactly as in C and apply to the nearest enclosing loop only.
  • switch is a GNU AWK extension — for portable scripts prefer chained if/else.
  • Multiple rules can fire on the same input record; they execute top-to-bottom in source order unless one of them calls next.

Running Today

All examples can be run using Docker:

docker pull alpine:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining