#!/bin/bash
help_text="
NAME
tot - Sum, average, min, max for a list of values with optional size units.
USAGE
tot [options] [values...]
OPTIONS
-h|--help
Show help text.
-u [<unit>]
Display values with size units. Without a value, auto-selects the
best unit. With a value (b, kb, mb, gb, tb), displays all values
in that unit. Units are also shown automatically when any input
value includes a unit suffix.
DESCRIPTION
Calculates sum, average, min and max for a list of numeric values.
Values can include size units: b, kb, mb, gb, tb (e.g. 2.5gb, 300kb).
Values without a unit are treated as plain numbers.
If values are passed as arguments they are used directly. Otherwise
an interactive prompt reads values (one or many per line) until Ctrl-D.
AUTHOR
mjnurse.github.io - 2026
"
help_line="Sum, average, min, max calculator with size unit support"
web_desc_line="Sum, average, min, max calculator with size unit support"
tmp="${help_text##*USAGE}"
usage=$(echo "Usage: ${tmp%%OPTIONS*}" | tr -d "\n" | sed "s/ */ /g")
show_units=false
forced_unit=""
while [[ "$1" != "" ]]; do
case $1 in
-h|--help)
echo "$help_text"
exit
;;
-u)
show_units=true
if [[ -n "$2" && "$2" =~ ^(b|kb|mb|gb|tb)$ ]]; then
forced_unit="$2"
shift
fi
;;
*)
break
;;
esac
shift
done
has_unit_input=false
parse_token() {
local token=$1
local val=$(echo "$token" | grep -oiE '^[0-9]*\.?[0-9]+')
local unit=$(echo "$token" | grep -oiE '[a-z]+$' | tr '[:upper:]' '[:lower:]')
if [[ -z "$val" ]]; then
echo "Skipping invalid: $token" >&2
return
fi
local bytes
case "$unit" in
b) bytes=$(echo "$val" | awk '{printf "%.0f", $1}'); has_unit_input=true ;;
kb) bytes=$(echo "$val" | awk '{printf "%.0f", $1 * 1024}'); has_unit_input=true ;;
mb) bytes=$(echo "$val" | awk '{printf "%.0f", $1 * 1024 * 1024}'); has_unit_input=true ;;
gb) bytes=$(echo "$val" | awk '{printf "%.0f", $1 * 1024 * 1024 * 1024}'); has_unit_input=true ;;
tb) bytes=$(echo "$val" | awk '{printf "%.0f", $1 * 1024 * 1024 * 1024 * 1024}'); has_unit_input=true ;;
"") bytes=$(echo "$val" | awk '{printf "%.0f", $1}') ;;
*) echo "Unknown unit '$unit' in: $token" >&2; return ;;
esac
values+=("$bytes")
}
values=()
if [[ $# -gt 0 ]]; then
for token in "$@"; do
parse_token "$token"
done
else
echo "Enter values (e.g. 100 2.5gb 300kb), Ctrl-D to finish:"
echo ""
while IFS= read -r line || [[ -n "$line" ]]; do
for token in $line; do
parse_token "$token"
done
done
fi
if [[ ${#values[@]} -eq 0 ]]; then
echo "No values entered."
exit 1
fi
fmt_val() {
local v=$1
local commas=$(printf "%0.f" "$v" | sed ':a;s/\B[0-9]\{3\}\>/,&/;ta')
if [[ "$show_units" == true || "$has_unit_input" == true ]]; then
local human
if [[ -n "$forced_unit" ]]; then
case "$forced_unit" in
b) human=$(awk -v b="$v" 'BEGIN { printf "%.0f b", b }') ;;
kb) human=$(awk -v b="$v" 'BEGIN { printf "%.2f kb", b / 1024 }') ;;
mb) human=$(awk -v b="$v" 'BEGIN { printf "%.2f mb", b / 1024^2 }') ;;
gb) human=$(awk -v b="$v" 'BEGIN { printf "%.2f gb", b / 1024^3 }') ;;
tb) human=$(awk -v b="$v" 'BEGIN { printf "%.2f tb", b / 1024^4 }') ;;
esac
else
human=$(awk -v b="$v" 'BEGIN {
if (b >= 1024^4) printf "%.2f tb", b / 1024^4
else if (b >= 1024^3) printf "%.2f gb", b / 1024^3
else if (b >= 1024^2) printf "%.2f mb", b / 1024^2
else if (b >= 1024) printf "%.2f kb", b / 1024
else printf "%.0f b", b
}')
fi
echo "$commas ($human)"
else
echo "$commas"
fi
}
sum=0
min=${values[0]}
max=${values[0]}
for v in "${values[@]}"; do
sum=$(echo "$sum + $v" | bc)
if (( $(echo "$v < $min" | bc -l) )); then min=$v; fi
if (( $(echo "$v > $max" | bc -l) )); then max=$v; fi
done
avg=$(echo "scale=0; $sum / ${#values[@]}" | bc)
echo ""
echo "Count: ${#values[@]}"
echo "Sum: $(fmt_val $sum)"
echo "Avg: $(fmt_val $avg)"
echo "Min: $(fmt_val $min)"
echo "Max: $(fmt_val $max)"