BASH中的数字格式与千位分隔符

Shi*_*dim 17 unix bash localization number-formatting

我有一个号码12343423455.23353.我想用千位分隔符格式化数字.所以输出就是 12,343,423,455.23353

Ign*_*ams 35

$ printf "%'.3f\n" 12345678.901
12,345,678.901
Run Code Online (Sandbox Code Playgroud)

  • C/POSIX 语言环境中没有千位分隔符,因此如果语言环境变量未设置,`printf "%'.3f\n" 12345678.901` 将打印 `12345678.901`。`LC_ALL=en_US.UTF-8 printf "%'.3f\n"` 总是使用逗号作为千位分隔符。 (3认同)
  • 找到它,*\`''将数字分成由"LC_NUMERIC"类别指定的语言环境指定的组* (2认同)
  • @user495470 您的解决方案在大多数环境中都会失败,因为它没有将 `LC_ALL` 传递给 `printf`,这是一个内置的 shell。因此,`printf` [没有自己的执行环境](http://stackoverflow.com/a/10939280);它实际上从未在命令行中_看到_`LC_ALL=en_US.UTF-8`。(只需在空环境中尝试并观察它失败。)该问题可以通过添加 `env`(GNU coreutils 和 BSD 环境都附带)来解决:`env LC_ALL=en_US.UTF-8 printf "% '.f\n"`。这适用于任何平台,以及 GNU Bash v3 或 v4。 (2认同)

mkl*_*nt0 8

TL;博士

  • 使用numfmt,如果GNU实用程序可用,比如Linux上的默认:

    • numfmt --grouping 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US
  • 否则,使用printf包含'shell函数字段标志,该函数保留输入小数位数(不会硬编码输出小数位数).

    • groupDigits 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US
    • 有关定义,请参阅本答案的底部,该定义groupDigits()还支持多个输入数字.
  • 特设的替代品,涉及子shell还保留的输入小数位数(假定输入小数点符号或者是.,):

    • 模块化但有些低效的变体,它通过stdin接受输入数字(因此也可以与管道输入一起使用):
      (n=$(</dev/stdin); f=${n#*[.,]}; printf "%'.${#f}f\n" "$n") <<<12343423455.23353
    • 使用中间变量的速度明显更快,但模块化程度更低$n: n=12343423455.23353; (f=${n#*[.,]} printf "%'.${#f}f\n" "$n")
  • 或者,考虑使用我的Linux/macOS grpCLI(可安装npm install -g grp-cli):

    • grp -n 12343423455.23353

在所有情况下都有警告 ; 见下文.


Ignacio Vazquez-Abrams的答案包含用于的关键指针printf:'字段标志(跟随%)格式化一个带有活动区域设置的千位分隔符的数字:

  • 请注意,man printf(man 1 printf)本身不包含此信息:实用程序/shell内置printf最终调用库函数 printf(),并且仅man 3 printf提供有关支持格式的完整图片.
  • 环境变量,LC_NUMERIC并间接地LANGLC_ALL控制与数字格式相关的活动区域设置.
  • numfmtprintf尊重有源区域设置,两者相对于所述千位分隔和十进制标记("小数点").
  • 只用printf本身,作为伊格纳西奥的答案,需要你硬编码的数字输出小数,而不是保留然而,许多小数输入了; 这是groupDigits()下面克服的限制.
  • printf "%'.<numDecPlaces>f"确实有一个优势numfmt --grouping,但是:
    • numfmt只接受十进制数,而printf's %f也接受十六进制整数(例如0x3e8)和十进制科学数字表示的数字(例如1e3).

注意事项

  • 不进行分组区域设置:某些地区,特别是CPOSIX,顾名思义不要申请分组,所以使用'在该事件没有影响.

  • 跨平台的实际区域设置不一致:

    • (LC_ALL='de_DE.UTF-8'; printf "%'.1f\n" 1000) # SHOULD yield: 1.000,0
    • Linux:1.000,0正如预期的那样收益率.
    • macOS/BSD:意外收益1000,0- 没有分组(!).
  • 输入数字格式:当您将数字传递给numfmt或时printf,它:
    • 必须不包含数字分组
    • 必须已使用活动区域设置的小数点
    • 例如:
      • (LC_ALL='lt_LT.UTF-8'; printf "%'.1f\n" 1000,1) # -> '1 000,1'
      • OK:输入数字未分组,并使用立陶宛小数点(逗号).
  • 可移植性:POSIX不要求printf 实用程序(如相对于在C printf() 库函数),以支持浮点格式的字符,如%f,假定POSIX [样]壳是整数仅; 但实际上,我并不知道任何没有的shell /平台.

  • 舍入错误和溢出:

    • 当使用numfmtprintf如上所述时,发生往返转换(字符串 - >数字 - >字符串),这可能会出现舍入错误; 换句话说:使用数字分组重新格式化可能会导致不同的数字.
    • 使用格式字符f采用IEEE-754双精度浮点值,只有最多15 显著位(不考虑小数点标记的位置的位)都保证要保持精度(尽管具体数字可能有更多的数字工作).在实践中,numfmt并且GNU printf可以精确地处理更多的比; 见下文.如果有人知道如何以及为什么,请告诉我.
    • 太多显著数字或当前过大的值时,行为之间不同numfmt并且printf在一般情况下,和之间printf跨平台的实现 ; 例如:

numft:

[修正了coreutils 8.24,根据@pixelbeat ]从20位有效数字开始,值溢出(!) - 可能是一个错误(从GNU coreutils 8.23开始):

# 20 significant digits cause quiet overflow:
$ (fractPart=0000000000567890; num="1000.${fractPart}"; numfmt --grouping "$num")
-92.23372036854775807    # QUIET OVERFLOW
Run Code Online (Sandbox Code Playgroud)

相反,默认情况下,数字太大会产生错误.

printf:

Linux 准确printf处理多达20位有效数字,而BSD/macOS实现仅限于17:

# Linux: 21 significant digits cause rounding error:
$  (fractPart=00000000005678901; num="1000.${fractPart}"; printf "%'.${#fractPart}f\n" "$num")
1,000.00000000005678902  # ROUNDING ERROR

# BSD/macOS: 18 significant digits cause rounding error:
$  (fractPart=00000000005678; num="1000.${fractPart}"; printf "%'.${#fractPart}f\n" "$num")
1,000.00000000005673  # ROUNDING ERROR
Run Code Online (Sandbox Code Playgroud)

Linux版本似乎永远不会溢出,而BSD/macOS版本报告错误的数字太大.


Bash shell功能groupDigits():

# SYNOPSIS
#   groupDigits num ...
# DESCRIPTION
#   Formats the specified number(s) according to the rules of the
#   current locale in terms of digit grouping (thousands separators).
#   Note that input numbers
#     - must not already be digit-grouped themselves,
#     - must use the *current* locale's decimal mark.
#   Numbers can be integers or floats.
#   Processing stops at the first number that can't be formatted, and a
#   non-zero exit code is returned.
# CAVEATS
#   - No input validation is performed.
#   - printf(1) is not guaranteed to support non-integer formats by POSIX,
#     though not doing so is rare these days.
#   - Round-trip number conversion is involved (string > double > string)
#     so rounding errors can occur.
# EXAMPLES
#   groupDigits 1000 # -> '1,000'
#   groupDigits 1000.5 # -> '1,000.5'
#   (LC_ALL=lt_LT.UTF-8; groupDigits 1000,5) # -> '1 000,5'
groupDigits() {
  local decimalMark fractPart
  decimalMark=$(printf "%.1f" 0); decimalMark=${decimalMark:1:1}
  for num; do
    fractPart=${num##*${decimalMark}}; [[ "$num" == "$fractPart" ]] && fractPart=''
    printf "%'.${#fractPart}f\n" "$num" || return
  done
}
Run Code Online (Sandbox Code Playgroud)

  • 唯一的解决方案,用重定向管道的话,谢谢! (3认同)