DEV Community

Cover image for AWK Sum Column: Total, Average, Min, Max and Group By
devTalk
devTalk

Posted on Originally published at dev-talk.com

AWK Sum Column: Total, Average, Min, Max and Group By

Adding up a column of numbers is one of the best reasons to learn AWK. There is no spreadsheet to open and no script to write: one line reads the file, adds each value to a running total, and prints the result at the end. This guide starts with that one line and then covers the things people actually need next: average, minimum and maximum, totals per group, and what to do when the data is messy. Every example comes with the input and the exact output.

The quick answer

awk '{sum += $1} END {print sum}' numbers.txt                # sum of column 1
awk -F, 'NR>1 {sum += $4} END {print sum}' sales.csv         # CSV: skip header, sum column 4
Enter fullscreen mode Exit fullscreen mode

New to AWK? The beginner's guide explains the pattern {action} idea used here, and how to print a column explains $1, $4 and NR.

Sample files

printf '10\n20\n30.5\n' > nums.txt
printf '100\n9\n25\n' > n3.txt
printf '5\nN/A\n7\n' > bad.txt
printf '1 2 3\n4 5 6\n' > grid.txt
: > empty.txt        # an empty file

cat > sales.csv <<'EOF'
date,region,product,qty,price
2026-10-01,north,pen,10,1.50
2026-10-01,south,book,3,12.00
2026-10-02,north,book,2,12.00
2026-10-02,east,pen,25,1.50
2026-10-03,south,pen,5,1.50
EOF
Enter fullscreen mode Exit fullscreen mode

Sum a column

awk '{sum += $1} END {print sum}' nums.txt
# 60.5
Enter fullscreen mode Exit fullscreen mode

Here is what happens. For every line, sum += $1 adds the first field to sum. You never declared sum: AWK creates a variable the first time you use it and starts it at zero. The END block runs once, after the last line, and prints the total.

For a CSV with a header, add -F, for the separator and NR>1 to skip the header line:

awk -F, 'NR>1 {s += $4} END {print s}' sales.csv
# 45
Enter fullscreen mode Exit fullscreen mode

Leaving out NR>1 does not change this total, because the header text qty counts as 0. But it quietly breaks the next calculation.

Count rows and calculate the average

Keep a counter next to the sum, then divide at the end:

awk -F, 'NR>1 {s += $4; n++} END {print s, n, s/n}' sales.csv
# 45 5 9
Enter fullscreen mode Exit fullscreen mode

Now see what happens without the header skip:

awk -F, '{s += $4; n++} END {print s, n, s/n}' sales.csv
# 45 6 7.5
Enter fullscreen mode Exit fullscreen mode

The sum is the same, but the header was counted as a sixth row, so the average is wrong and nothing warns you.

Guard against an empty file

If there is no input, n is never set, and the division goes wrong. The two awks I tested disagree on how:

awk '{s += $1; n++} END {print s/n}' empty.txt
# gawk: fatal: division by zero attempted
# mawk: -nan
Enter fullscreen mode Exit fullscreen mode

gawk stops with an error. mawk prints -nan and carries on, which is easy to miss in a script. Check before you divide:

awk '{s += $1; n++} END {if (n > 0) print s/n; else print "no data"}' empty.txt
# no data
Enter fullscreen mode Exit fullscreen mode

Minimum and maximum

Start both from the first data row, then compare every row against them:

awk -F, 'NR==2 {min=max=$4}
         NR>2  {if ($4<min) min=$4; if ($4>max) max=$4}
         END   {print "min", min, "max", max}' sales.csv
# min 2 max 25
Enter fullscreen mode Exit fullscreen mode

The comparison is numeric, not alphabetical. On the file n3.txt (100, 9, 25), the minimum comes out as 9 and the maximum as 100, which is correct. If the comparison were on text, "9" would beat "100".

Do not start the minimum at 0 in a BEGIN block. Then nothing in a list of positive numbers is ever smaller, and you get 0:

awk 'BEGIN{min=0} {if ($1<min) min=$1} END {print min}' n3.txt
# 0   (wrong, the real minimum is 9)
Enter fullscreen mode Exit fullscreen mode

If the data may not have a header, or you do not know the first row's line number, use a version that treats "not set yet" as the starting point:

awk '{if (min=="" || $1<min) min=$1; if (max=="" || $1>max) max=$1} END {print min, max}' n3.txt
# 9 100
Enter fullscreen mode Exit fullscreen mode

Sum only some rows

Put a condition in front of the braces. Total quantity sold in the north region:

awk -F, '$2=="north" {s += $4} END {print s}' sales.csv
# 12
Enter fullscreen mode Exit fullscreen mode

To count matching rows, use a counter. Add +0 when you print it, so that zero matches prints 0 instead of an empty line:

awk -F, 'NR>1 && $2=="north" {n++} END {print n+0}' sales.csv
# 2
Enter fullscreen mode Exit fullscreen mode

Totals per group

An array indexed by the group name gives you one total per group. This is the AWK version of a spreadsheet pivot table:

awk -F, 'NR>1 {s[$2] += $4} END {for (k in s) print k, s[k]}' sales.csv | sort
# east 25
# north 12
# south 8
Enter fullscreen mode Exit fullscreen mode

The | sort is there because AWK does not promise any order when looping over an array; gawk and mawk printed the same groups in a different order when I left it out.

The same idea works for money. Revenue is quantity times price, so add that product per region, plus a grand total:

awk -F, 'NR>1 {r[$2] += $4 * $5; t += $4 * $5}
         END {for (k in r) print k, r[k]; print "total", t}' sales.csv | sort
# east 37.5
# north 39
# south 43.5
# total 120
Enter fullscreen mode Exit fullscreen mode

Sum across each row, or down each column

The file grid.txt has two lines of three numbers. A loop over the fields gives the total of each row:

awk '{t=0; for (i=1; i<=NF; i++) t += $i; print t}' grid.txt
# 6
# 15
Enter fullscreen mode Exit fullscreen mode

To total every column at once, keep an array indexed by column number:

awk '{for (i=1; i<=NF; i++) c[i] += $i}
     END {for (i=1; i<=NF; i++) printf "%s%s", c[i], (i<NF ? OFS : ORS)}' grid.txt
# 5 7 9
Enter fullscreen mode Exit fullscreen mode

Inside END, NF still holds the field count of the last line, which is fine when every line has the same number of columns.

Running total

Print inside the main rule, not in END, to show the total so far on every line:

awk -F, 'NR>1 {s += $4; print $4, s}' sales.csv
# 10 10
# 3 13
# 2 15
# 25 40
# 5 45
Enter fullscreen mode Exit fullscreen mode

Percent of total (reading the file twice)

To show each row as a share of the total, AWK needs the total first. Pass the same file twice. NR==FNR is true only while the first copy is being read, so you can add things up on the first pass and print on the second:

awk -F, 'NR==FNR { if (FNR>1) t += $4; next }
         FNR>1  { printf "%-5s %5.1f%%\n", $3, $4/t*100 }' sales.csv sales.csv
# pen    22.2%
# book    6.7%
# book    4.4%
# pen    55.6%
# pen    11.1%
Enter fullscreen mode Exit fullscreen mode

Print the result properly

AWK's plain print shows a number with at most six significant digits, so a large decimal total turns into scientific notation. I tested this on both awks:

awk 'BEGIN {print 1234567.89; printf "%.2f\n", 1234567.89}'
# 1.23457e+06
# 1234567.89
Enter fullscreen mode Exit fullscreen mode

For money and anything you plan to copy into a report, use printf "%.2f\n". Whole numbers are fine either way (100000 * 3 prints 300000), and 0.1 + 0.2 prints 0.3 because of the same six-digit rounding.

When the data is messy

Text where a number should be

AWK converts text to the number it starts with, and to 0 if it starts with no number. A row of N/A is added as 0, with no error:

awk '{s += $1; n++} END {print s, n, s/n}' bad.txt
# 12 3 4
Enter fullscreen mode Exit fullscreen mode

The sum is right, but the average divides by three rows when only two have numbers. Filter with a pattern, and the count and the sum both agree:

awk '$1 ~ /^[0-9.]+$/ {s += $1; n++} END {print s, n, s/n}' bad.txt
# 12 2 6
Enter fullscreen mode Exit fullscreen mode

Currency symbols and thousand separators

A value like $1,200.50 starts with a dollar sign, so AWK reads it as 0. Strip the symbol and the comma first with gsub:

printf 'price\n$1,200.50\n$99.25\n' > money.txt
awk '{s += $1} END {print s}' money.txt
# 0

awk 'NR>1 {gsub(/[$,]/, "", $1); s += $1} END {printf "%.2f\n", s}' money.txt
# 1299.75
Enter fullscreen mode Exit fullscreen mode

(I skipped the price header with NR>1; it would add 0 either way.) A comma used as a thousands separator also clashes with -F,. A quoted "1,200" in a CSV file is split into two fields, which is a job for the CSV article in this series.

Numbers with a unit

AWK reads the leading number and ignores the rest, so 20G plus 120G adds to 140:

printf 'Used\n20G\n0\n120G\n' | awk 'NR>1 {s += $1+0} END {print s}'
# 140
Enter fullscreen mode Exit fullscreen mode

That works only when every value has the same unit. A column that mixes M and G will add the digits as if they were the same thing.

Strings that look like hex

One difference between the two awks: a value such as 0x1A is 0 in gawk and 26 in mawk when used as a number. If your data contains hex-looking codes, do not rely on arithmetic on them.

Quick reference

Goal Command
Sum awk '{s+=$1} END {print s}'
Sum, skipping a header awk 'NR>1 {s+=$1} END {print s}'
Average awk '{s+=$1; n++} END {if (n) print s/n}'
Count awk 'END {print NR}'
Max `awk '{if (max=="" \
Min {% raw %}`awk '{if (min=="" \
Sum per group {% raw %}`awk '{s[$1]+=$2} END {for (k in s) print k, s[k]}' \
Two decimals {% raw %}printf "%.2f\n", s

Frequently asked questions

How do I sum a column in AWK?

Add each value to a variable in the main rule and print it in END: awk '{s += $3} END {print s}' file.

How do I sum a column in a CSV file?

Add the separator and skip the header: awk -F, 'NR>1 {s += $4} END {print s}' file.csv.

Why does my total print as 1.23457e+06?

Plain print limits numbers to six significant digits. Use printf "%.2f\n", total.

Can AWK sum a column that has a header?

Yes. Skip the first line with NR>1, so the header is neither added nor counted.


Originally published on DEV Talk.

Top comments (0)