Format currency in Bash
,我想知道如何确定哪些字符用作数字分隔符。
关于区域设置和数字格式存在很多问题,例如:
printf '%.5f\n' $(bc -l <<<'4*a(1)')
3.14159
LANG=de_DE printf '%.5f\n' $(bc -l <<<'4*a(1)')
bash: printf: 3.14159265358979323844: invalid number
3,00000
二进制计算器
bc
似乎无法正确处理区域设置...
在提到的答案下,搜索小数分隔符(或基数字符),我用过这个:
int2amount() {
local TIMEFORMAT=%U _decsep
read _decsep < <(eval 'time true' 2>&1)
_decsep=${_decsep//[0-9]}
...
}
这个工作正常:
pi() { local TIMEFORMAT=%U _decsep;read _decsep < <(eval 'time true' 2>&1);_decsep=${_decsep//[0-9]};
local pi=$(bc -l <<<'4*a(1)')
printf '%.5f\n' ${pi/./$_decsep}
}
pi
3.14159
LANG=de_DE pi
3,14159
但是因为千位分隔符更容易找到:
printf -v ts "%'d" 1111 ; ts=${ts//1}
没有分叉,因此系统占用空间非常轻。
所以我可以想象在源文件的开头,类似:
numericSeparators() {
local TIMEFORMAT=%U
read NUM_DEC_SEP < <(eval 'time true' 2>&1)
NUM_DEC_SEP=${NUM_DEC_SEP//[0-9]}
printf -v NUM_THO_SEP "%'d" 1111
NUM_THO_SEP=${NUM_THO_SEP//1}
}
numericSeparators
declare -r NUM_THO_SEP NUM_DEC_SEP
...
但我认为
<(eval 'time true' 2>&1)
对于目标来说很沉重。我正在寻找一种更轻和/或更干净的方法来确定它们(甚至是decimal和thousand分隔符)。
自我回答
感谢dan的回答,我的功能会变得更简单更快!
样本正确/适应
bc
的输出:
pi() {
local _decsep pi=$(bc -l <<<'4*a(1)')
printf -v _decsep %.1f 1
printf '%.5f\n' ${pi/./${_decsep:1:1}}
}
pi
3.14159
LANG=de_DE.UTF-8 pi
3,14159
一个小函数,将设置两个变量:
NUM_THO_SEP
用于千位分隔符和NUM_DEC_SEP
用于小数分隔符:
numericSeparators() {
local numtest
printf -v numtest "%'.1f" 1111
NUM_THO_SEP=${numtest:1:1}
NUM_THO_SEP=${NUM_THO_SEP/1}
NUM_DEC_SEP=${numtest: -2:1}
}
numericSeparators
for loctest in {C,en_US,fr_{CH,FR},de_{CH,DE},it_{CH,IT}}{,.UTF8} ;do
LANG=$loctest numericSeparators
LANG=C printf 'LANG=%-12s thsnd=%-1s \e[2m(%q)\e[0m\e[45G radix=%q\n' \
"$loctest" "$NUM_THO_SEP"{,} "$NUM_DEC_SEP"
done
LANG=C thsnd= ('') radix=.
LANG=C.UTF8 thsnd= ('') radix=.
LANG=en_US thsnd=, (\,) radix=.
LANG=en_US.UTF8 thsnd=, (\,) radix=.
LANG=fr_CH thsnd=' (\') radix=.
LANG=fr_CH.UTF8 thsnd=’ ($'\342\200\231') radix=.
LANG=fr_FR thsnd=�($'\240') radix=\,
LANG=fr_FR.UTF8 thsnd= ($'\342\200\257') radix=\,
LANG=de_CH thsnd=' (\') radix=.
LANG=de_CH.UTF8 thsnd=’ ($'\342\200\231') radix=.
LANG=de_DE thsnd=. (.) radix=\,
LANG=de_DE.UTF8 thsnd=. (.) radix=\,
LANG=it_CH thsnd=' (\') radix=.
LANG=it_CH.UTF8 thsnd=’ ($'\342\200\231') radix=.
LANG=it_IT thsnd=. (.) radix=\,
LANG=it_IT.UTF8 thsnd=. (.) radix=\,
注意:由于我的终端是 UTF-8,他们无法以纯 ASCII (
NO-BREAKABLE SPACE
) 打印出 $'\240'
。这是因为它们显示了 替换字符: �
。
您可以通过以下方式获取语言环境的基数字符(小数分隔符):
printf -v ds '%#.1f' 1
ds=${ds//[0-9]}
还有千位分组分隔符,其中:
printf -v ts "%'d" 1111
ts=${ts//1}
某些语言环境(例如
C
)没有千位分隔符,在这种情况下 $ts
为空。相反,如果区域设置未定义基数字符,则 POSIX (printf(3)
) 表示它应默认为 .
。 #
标志保证它将被打印。
在绝大多数情况下,您甚至不必知道所处的区域设置即可正确解码任何值,无论您自己的区域设置如何。
因为任何底数都不可能有 2 个小数点 (RP),所以可以使用
gsub()
或类似的快速计数工具来找出 ,
与 .
中哪一个有多个副本。
如果两者都存在,那么这可能是有问题的输入。
如果两者都存在,则右侧的一个必须是 RP
当只有一个且不明确时,请考虑:
如果该字符右侧有 0 个或更多数字,但不是 3 位数字,则不可能是千位
更有可能的是,数千 sep 的两侧都以数字结尾,但具有前缘或后缘小数点并不罕见
只有 4 到 6 位数字(包括小数点以下,假设其仍然不明确)需要额外的上下文才能正确解码。