Jason Aruni
Jason Aruni

Reputation: 33

Format currency in Bash

Is it possible to format currency in Bash?

Example data is received as 19366 Data to be displayed as $193,66

Thanks.

Upvotes: 1

Views: 1093

Answers (6)

duise
duise

Reputation: 51

The question actually mixes two different problems:

  1. Converting cents to US-Dollar (or Euros, or whatever currency)
  2. Using the correct currency format (currency_symbol, mon_decimal_point, mon_thousands_sep, ...)

The first can be solved by using either external tools like bc or a shell that supports floating point operations like ksh. Bash only supports integer arithmetic.

The latter can be solved by using locale information. See here for an example.

Upvotes: 0

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 70822

Preamble In your request, you use coma , as decimal separator (radix mark). For locale support, see second part of my answer.

1. Pseudo floating poing using integer as strings

I often use this kind of pseudo float:

amount=123456
amount=00$amount # avoid bad length error
printf '$%.2f\n' ${amount::-2}.${amount: -2}
$1234.56
for amount in  0 1 12 123 1234 12345;do
    amount=00$amount
    printf '$%.2f\n' ${amount::-2}.${amount: -2}
done
$0.00
$0.01
$0.12
$1.23
$12.34
$123.45

As a function:

int2amount() { 
    if [[ $1 == -v ]]; then
        local -n _out="$2"
        shift 2
    else
        local _out
    fi
    local _amount=00$(($1))
    printf -v _out $'$%\47.2f' ${_amount::-2}.${_amount: -2}
    [[ ${_out@A} != _out=* ]] || echo "$_out"
}

Then

int2amount 123456
$1’234.56
int2amount -v var 1234567
echo $var
$12’345.67

2. Remark regarding locale, decimal separator and thousand separators

In your request, your radix mark is a coma ,. This depend on your locale configuration. U could hit something like:

set | grep ^LC\\\|^LANG

to show how this is configured on your host.

As there are many issues regarding locales, I've asked How to determine which character is used as decimal separator or thousand separator under current locale as separated question.

Try:

for locvar in   C    en_US.UTF-8    de_DE.UTF-8   ;do
    LC_NUMERIC=$locvar int2amount 1234567
done
$12345.67
$12,345.67
bash: line 1: printf: 0012345.67: invalid octal number
$12.345,00

Error because unsing de_DE locale configuration, you have to use a coma as separator (Decimal separator at wikipedia).

This is already know to produce issues using bc: How do I change the decimal separator in the printf command in bash?

Final function unsing variable decimal separator

int2amount () {
    if [[ $1 == -v ]]; then
        local -n _out="$2"
        shift 2
    else
        local _out
    fi
    local _amount=00$(($1)) _decsep
    printf -v _decsep %.1f 1
    _decsep=${_decsep:1:1}
    printf -v _out '$%'\''.2f' ${_amount::-2}${_decsep}${_amount: -2}
    [[ ${_out@A} != _out=* ]] || echo "$_out"
}
for locvar in   C    en_US.UTF-8    de_DE.UTF-8   ;do
    LC_NUMERIC=$locvar int2amount 1234567
done
$12345.67
$12,345.67
$12.345,67

Note about LC_ALL: If in your environment, a variable $LC_ALL is defined, all demos using LC_NUMERIC won't work because LC_ALL is over. You have to unset LC_ALL or use:

LC_ALL=$locvar LC_NUMERIC=$locvar int2amount 1234567

in last demo.

Upvotes: 3

Paul Hodges
Paul Hodges

Reputation: 15313

Simplistically -

$: sed -E 's/([0-9]*)([0-9][0-9])$/$\1,\2/'<<<"19366"
$193,66

Upvotes: 0

dan
dan

Reputation: 5241

printf does have a thousands grouping format specifier flag, however the character used to denote the groups (non-monetary grouping character) depends on locale (LC_NUMERIC).

The C or POSIX locale uses no grouping character. Therefore you can't do this portably with printf.

printf "%'d\n" 19366

Works if the current locale supports the comma grouping character.

In my bashrc, I use the following function to add thousands groupings to any integer, using comma (,) and preserving a non numeric prefix (like $, or - for negative numbers). It doesn't depend on locale, but does require rev.

commafy ()
{
    printf %s "${1%%[0-9]*}"
    printf '%s\n' "${1##*[!0-9]}" |
    rev |
    sed -E 's/[0-9]{3}/&,/g; s/,$//' |
    rev
}

Example:

commafy '$19366'
# gives
$19,366

You could slightly simplify this too:

printf %s \$
printf '%s\n' 19366 |
rev |
sed -E 's/[0-9]{3}/&,/g; s/,$//' |
rev

Upvotes: 0

Renaud Pacalet
Renaud Pacalet

Reputation: 29222

Simply handle your value as a text string, instead of a number, and insert a dollar sign and a comma at the correct positions:

$ v=19366
$ printf '$%s,%s\n' "${v:0: -2}" "${v: -2}"
$193,66

${v:offset:length) expands as the substring of $v that starts at character offset (counting from 0) and which length is length. But negative offsets and lengths can be used to refer to the end of the string.

${v:0:-2} expands as the substring of $v that starts at the beginning (0) and which length is the number of remaining characters minus two (-2). In our example this is 193.

${v: -2} expands as the substring of $v that starts two characters before the end (-2) and which length (not specified) is the number of remaining characters. In our example this is 66. Note the space between : and -2, it is needed to avoid another interpretation by the shell (providing default value 2 if v is unset or null).

Upvotes: 4

Diego Velez
Diego Velez

Reputation: 1893

You can use printf

amount="240570.578"
printf "%'.2f\n" $amount
> 240,570.58

Upvotes: 1

Related Questions