Bash环境下如何获取当前locale的小数分隔符与千位分隔符
Bash中获取当前Locale的数字分隔符
问题背景
之前做Bash下货币格式化相关需求时,需要确定当前系统locale配置下使用的数字分隔符,过程中碰到了不少locale和数字格式化的兼容问题:二进制计算器bc完全不遵循locale规则,始终用.作为小数点输出,和locale配置不匹配时会直接触发报错:
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
早期实现方案
最开始查找小数分隔符(也叫基数字符/radix character)的时候,用的是借助time命令输出的取巧方案:
int2amount() { local TIMEFORMAT=%U _decsep read _decsep < <(eval 'time true' 2>&1) _decsep=${_decsep//[0-9]} ... }
这个方案可以正常运行,比如适配bc输出圆周率的函数:
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
而千位分隔符的获取很早就有轻量实现,不需要额外fork进程,系统开销极低:
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)写法需要创建进程替换,实现过于笨重,我一直想找更简洁轻量的方法,不用额外开进程就能同时拿到两个分隔符。
自答
参考dan的实现思路后,我最终写出了非常简洁高效的版本。
适配bc输出的示例
修正bc输出和locale不匹配问题的圆周率计算函数如下:
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存储小数分隔符,全程无额外进程fork,开销极低:
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
多Locale测试
我在多个常用locale下做了兼容性测试,测试代码如下:
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编码,非UTF-8 locale下的不间断空格($'\240')无法以纯ASCII形式正常打印,因此会显示替换字符�。
内容的提问来源于stack exchange,提问作者F. Hauri - Give Up GitHub
相关产品推荐
相关产品推荐

