0Pricing
DevOps Bootcamp · Lesson

Aggregation with awk Arrays and Grouping

Compute sums, counts, and group-by summaries using associative arrays keyed on field values.

Aggregation with awk Arrays and Grouping is a free DevOps Bootcamp lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DevOps Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Are awk Associative Arrays?

In awk, an associative array is a key-value store where keys can be any string or number. Unlike indexed arrays in most languages, awk arrays are hash maps under the hood — perfect for grouping and aggregating data by field values.

  • Declare implicitly: just assign arr[key] = value
  • Keys are strings by default (numbers are coerced)
  • No size limit — awk grows the array as needed
  • Ideal for computing sums, counts, and group-by rollups in a single pass

You do not need to initialize a key before incrementing it — awk treats an unset key as zero or an empty string automatically.

Counting Lines Per Group

The most common aggregation pattern is counting how many times each unique value in a field appears. Use field $1 (or any field) as the array key and increment a counter on every matching row.

The END block runs after all input is consumed — that is where you print the accumulated results.

count[$1]++

After processing, count holds the total number of lines for each unique value of $1.

#!/usr/bin/env bash
# Count how many log entries exist per HTTP status code
# Input format: <ip> <date> <method> <path> <status> <bytes>
printf '10.0.0.1 2024-01-01 GET /index 200 512
10.0.0.2 2024-01-01 POST /api 404 128
10.0.0.3 2024-01-01 GET /img 200 2048
10.0.0.4 2024-01-01 GET /api 500 64
10.0.0.5 2024-01-01 DELETE /api 404 32
' | awk '
{
    count[$5]++
}
END {
    for (status in count)
        print status, count[status]
}'

Summing a Numeric Field per Group

Counting is just one form of aggregation. To sum a numeric field grouped by another field, accumulate the numeric value into an array keyed by the group field.

The pattern is:

  • Key = the group-by field (e.g., $1 for username)
  • Value = running total of the numeric field (e.g., $2 for bytes)

This computes a full group-by sum in a single awk pass — no sorting or pre-processing required.

#!/usr/bin/env bash
# Sum bytes transferred per user from an access log
printf 'alice 1024
bob 512
alice 2048
charlie 256
bob 768
alice 128
' | awk '
{
    bytes[$1] += $2
}
END {
    for (user in bytes)
        printf "%-10s %d bytes\n", user, bytes[user]
}'

Combining Count and Sum in One Pass

awk lets you maintain multiple arrays simultaneously, giving you count and sum (and therefore average) for each group in one scan of the file. This is far more efficient than running the pipeline twice.

  • count[key]++ — tracks occurrences
  • total[key] += $N — tracks the running sum
  • In END, divide total[k] / count[k] for the per-group average
#!/usr/bin/env bash
# Compute per-department headcount and average salary
printf 'Engineering Alice 95000
Engineering Bob 88000
Marketing Carol 72000
Marketing Dave 68000
Engineering Eve 102000
Marketing Frank 75000
' | awk '
{
    dept  = $1
    sal   = $3
    count[dept]++
    total[dept] += sal
}
END {
    printf "%-15s %5s %10s\n", "Department", "Count", "Avg Salary"
    for (d in count)
        printf "%-15s %5d %10.0f\n", d, count[d], total[d]/count[d]
}'

Multi-Field Keys for 2-D Grouping

You can create a composite key by concatenating multiple fields with a separator. This gives you a two-dimensional group-by without any special syntax.

Choose a separator that cannot appear in the data (e.g., SUBSEP, the built-in awk record separator \034, or a literal pipe |).

  • Composite key: arr[$1 SUBSEP $2] or arr[$1"|"$2]
  • Split key back when printing: split(key, parts, "|")
#!/usr/bin/env bash
# Count requests grouped by HTTP method AND status code
printf 'GET 200
POST 201
GET 404
GET 200
DELETE 204
POST 201
GET 404
POST 500
' | awk '
{
    key = $1 "|" $2
    count[key]++
}
END {
    printf "%-8s %-6s %s\n", "Method", "Status", "Count"
    for (k in count) {
        split(k, parts, "|")
        printf "%-8s %-6s %d\n", parts[1], parts[2], count[k]
    }
}'

Tracking Minimum and Maximum per Group

Associative arrays make it easy to track min and max values per group. The trick is to initialize only on the first occurrence of each key using the special condition !(key in arr).

  • !(key in min_arr) is true the first time a key is seen
  • After initialization, compare and overwrite with the standard less-than / greater-than check

This pattern avoids a spurious zero that would corrupt your minimum.

#!/usr/bin/env bash
# Find min and max response time per endpoint
printf '/api/users 120
/api/orders 340
/api/users 98
/api/orders 512
/api/users 210
/api/orders 280
/health 5
/health 7
' | awk '
{
    ep  = $1
    rt  = $2
    if (!(ep in mn)) { mn[ep] = rt; mx[ep] = rt }
    if (rt < mn[ep]) mn[ep] = rt
    if (rt > mx[ep]) mx[ep] = rt
}
END {
    printf "%-15s %6s %6s\n", "Endpoint", "Min", "Max"
    for (ep in mn)
        printf "%-15s %6d %6d\n", ep, mn[ep], mx[ep]
}'

Frequency Distribution — Top-N Groups

A common real-world task is finding the top-N most frequent values. awk handles the counting; pipe through sort and head to rank and slice.

This pipeline pattern is idiomatic in shell engineering:

  1. awk counts occurrences into an array, prints count key in END
  2. sort -rn sorts numerically in descending order
  3. head -n 5 keeps only the top 5

Keeping awk focused on aggregation and delegating sorting to sort follows the Unix philosophy.

#!/usr/bin/env bash
# Top 3 most active IP addresses from an access log
printf '192.168.1.10 GET /index
10.0.0.5 POST /api
192.168.1.10 GET /css
172.16.0.3 GET /index
10.0.0.5 GET /api
192.168.1.10 DELETE /api
172.16.0.3 POST /login
10.0.0.5 GET /index
10.0.0.5 POST /logout
' | awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' \
  | sort -rn \
  | head -n 3

Using NR and FNR for Multi-File Aggregation

When processing multiple files, awk's built-in NR (total records read) and FNR (records in the current file) let you tag which file each record came from using FILENAME.

  • FILENAME — name of the file currently being read
  • FNR == 1 — triggers at the start of each new file (useful for skipping headers)

This allows per-file group-by aggregation across an entire directory of logs in one awk invocation.

Printing Sorted Output from awk Arrays

The for (key in array) loop in awk does not guarantee order. For deterministic output, pipe awk's END print to sort, or collect values and sort within awk using the asorti / asort functions (GNU awk only).

The portable shell-pipeline approach is usually preferred for cross-platform scripts:

  • sort -k1,1 — sort by the first field (group key) alphabetically
  • sort -k2,2rn — sort by second field (count/sum) numerically descending
#!/usr/bin/env bash
# Bytes per user, sorted alphabetically by username
printf 'zara 800
alice 1500
bob 300
alice 200
zara 400
bob 1100
' | awk '
{
    total[$1] += $2
}
END {
    for (u in total)
        print u, total[u]
}' | sort -k1,1

Deleting Array Keys and Clearing State

Sometimes you need to reset aggregation state mid-stream — for example, when processing log files where each section is separated by a blank line or a sentinel value.

  • delete arr[key] — removes a single entry
  • delete arr — clears the entire array (GNU awk)
  • Check existence with (key in arr) before accessing to avoid creating ghost entries

This technique enables sliding-window or per-section aggregation without restarting awk.

#!/usr/bin/env bash
# Sum values within each section delimited by "---"
printf 'alice 100
bob 200
---
alice 50
bob 75
charlie 300
---
' | awk '
/^---/ {
    print "-- Section totals --"
    for (u in total) printf "  %-10s %d\n", u, total[u]
    delete total
    next
}
{
    total[$1] += $2
}'

Real-World Pipeline: Nginx Log Summary

Putting it all together — here is a production-grade awk one-pass aggregation over an Nginx combined log format. It computes request count, total bytes, and error rate per virtual host in a single scan.

Key techniques used:

  • Composite key: vhost extracted from a field
  • Parallel arrays: reqs[], bytes[], errors[]
  • Conditional accumulation: $9 >= 400 to count only error responses
  • Formatted printf table in END
#!/usr/bin/env bash
# Simulated Nginx combined log: host ip - - [date] "METHOD /path HTTP/1.1" status bytes
printf 'api.example.com 10.0.0.1 - - [01/Jan/2024] "GET /v1" 200 1024
www.example.com 10.0.0.2 - - [01/Jan/2024] "GET /" 200 4096
api.example.com 10.0.0.3 - - [01/Jan/2024] "POST /v1" 500 128
www.example.com 10.0.0.4 - - [01/Jan/2024] "GET /img" 404 64
api.example.com 10.0.0.5 - - [01/Jan/2024] "GET /v1" 200 2048
www.example.com 10.0.0.6 - - [01/Jan/2024] "GET /" 200 4096
api.example.com 10.0.0.7 - - [01/Jan/2024] "DELETE /v1" 403 32
' | awk '
{
    host = $1; status = $9; b = $10
    reqs[host]++
    bytes[host] += b
    if (status + 0 >= 400) errors[host]++
}
END {
    printf "%-20s %6s %10s %6s\n", "Host", "Reqs", "Bytes", "Errors"
    for (h in reqs)
        printf "%-20s %6d %10d %6d\n", h, reqs[h], bytes[h], errors[h]+0
}'

Knowledge Check: Initializing Min Correctly

Test your understanding of a common awk aggregation pitfall.

Lesson Recap: Aggregation with awk Arrays

You have learned the core patterns for computing group-by aggregations entirely within awk:

  • Counting: count[$key]++ — increment on each row, print in END
  • Summing: total[$key] += $N — accumulate numeric fields per group
  • Average: maintain both count[] and total[], divide in END
  • Min/Max: seed on first occurrence using !(key in arr), then compare
  • Composite keys: concatenate fields with a separator for multi-dimensional grouping
  • Sorted output: pipe awk's END print to sort for deterministic ordering
  • Section resets: use delete arr to clear state between logical sections of input

These patterns let you replace heavy database queries or multiple pipeline passes with a single, efficient awk invocation — an essential skill for production shell engineering.

Frequently asked questions

Is the “Aggregation with awk Arrays and Grouping” lesson free?

Yes — the full text of “Aggregation with awk Arrays and Grouping” is free to read here on the web, and the DevOps Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DevOps Bootcamp course, upgrade to CoddyKit PRO.

What will I learn in “Aggregation with awk Arrays and Grouping”?

Compute sums, counts, and group-by summaries using associative arrays keyed on field values. You practise DevOps Bootcamp with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start DevOps Bootcamp?

No prior experience is required. DevOps Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Aggregation with awk Arrays and Grouping” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this DevOps Bootcamp lesson?

Yes. Every DevOps Bootcamp lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Records, Fields, and Custom Separators in awk
  2. Patterns, Ranges, and BEGIN/END Blocks
  3. Aggregation with awk Arrays and Grouping
  4. awk Functions, printf Formatting, and Report Generation
← Back to DevOps Bootcamp