Commifying Numbers

Problem

You’d like to add a thousands-place separator to long numbers.

Solution

Depending on your system and configuration, you may be able to use printf’s ' format flag with a suitable local. Thanks to Chet Ramey for this solution, which is by far the easiest if it works:

$ LC_NUMERIC=en_US.UTF-8 printf "%'d\n" 123456789
123,456,789

$ LC_NUMERIC=en_US.UTF-8 printf "%'f\n" 123456789.987
123,456,789.987000

Thanks to Michael Wang for contributing the following shell-only implementation and relevant discussion:

# cookbook filename: func_commify

function commify {
    typeset text=${1}

    typeset bdot=${text%%.*}
    typeset adot=${text#${bdot}}

    typeset i commified
    (( i = ${#bdot} - 1 ))

    while (( i>=3 )) && [[ ${bdot:i-3:1} == [0-9] ]]; do
        commified=",${bdot:i-2:3}${commified}"
        (( i -= 3 ))
    done
    echo "${bdot:0:i+1}${commified}${adot}"
}

Or you can try one of the sed solutions from the sed FAQ, for example:

sed ':a;s/\B[0-9]\{3\}\>/,&/;ta'  /path/to/file                     # GNU sed
sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta' /path/to/file  # other seds

Discussion

The shell function is written to follow the same logical process as a person using a pencil and paper. First you examine the string and find the decimal point, if any. You ignore everything after the dot, and work on the string before the dot.

The shell function saves the string before the dot in $bdot, and after the dot (including the dot) in $adot. If there is no dot, then everything is in $bdot, and $adot is empty. Next a person would ...

Get bash Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.