web-dev-qa-db-ja.com

千単位の区切り文字を使用したBASHでの数値の書式設定

番号12343423455.23353があります。千単位の区切り文字で数値をフォーマットしたい。したがって、出力は12,343,423,455.23353になります。

18
Shiplu Mokaddim
$ printf "%'.3f\n" 12345678.901
12,345,678.901

tl; dr

  • [〜#〜] gnu [〜#〜]の場合、numfmtを使用しますLinuxなどのユーティリティがデフォルトで利用可能です。

    • _numfmt --grouping 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US_
  • それ以外の場合、printfを使用し、_'_フィールドフラグをシェル関数そのは入力小数点以下の桁数を保持します出力の数をハードコーディングしません 小数位)。

    • _groupDigits 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US_
    • groupDigits()の定義については、この回答の下部を参照してください。これは、複数の入力番号もサポートします。
  • サブシェルを含むアドホックな代替手段も入力小数点以下の桁数を保持します(入力小数点が_._または_,_のいずれかであると想定):

    • 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/macOSgrp CLI(_npm install -g grp-cli_でインストール可能)の使用を検討してください:

    • _grp -n 12343423455.23353_

すべての場合において、警告;があります。下記参照。


Ignacio Vazquez-Abramsの回答printfで使用するための重要なポインタが含まれています:_'_フィールドフラグ(_%_に続く)は、アクティブなロケールで数値をフォーマットします千の区切り文字:

  • _man printf_(_man 1 printf_)には、この情報自体は含まれていないことに注意してください。utility/シェル組み込みprintfは最終的にlibrary functionprintf()であり、サポートされている形式に関する全体像を示すのは_man 3 printf_のみです。
  • 環境変数_LC_NUMERIC_と、間接的にLANGまたは_LC_ALL_は、数値の書式設定に関してアクティブなロケールを制御します。
  • numfmtprintfはどちらも、千単位の区切り文字と小数点(「小数点」)の両方に関して、アクティブなロケールを尊重します。
  • Ignacioの回答のように、printfだけを単独で使用するには、ハードコード出力の数が必要です。入力の小数点以下の桁数を保持するのではなく、小数点以下の桁数。以下のgroupDigits()が克服するのはこの制限です。
  • _printf "%'.<numDecPlaces>f"_には_numfmt --grouping_よりも1つの利点がありますが、
    • numfmt10進数のみを受け入れますが、printfの_%f_は16進数も受け入れます。整数(例:_0x3e8_)および10進数の数値科学的記数法(例:_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に数値を渡すと、次のようになります:
    • すでに数字のグループ化が含まれていてはならない
    • mustはすでにactiveロケールの小数点を使用している必要があります
    • 例えば:
      • _(LC_ALL='lt_LT.UTF-8'; printf "%'.1f\n" 1000,1) # -> '1 000,1'_
      • OK:入力番号はグループ化されておらず、リトアニア語の小数点(コンマ)を使用しています。
  • Portability:POSIXはrequireprintfutility (C printf()library functionとは対照的に)このような浮動小数点形式の文字をサポートしますPOSIX [-like]シェルが整数のみである場合、_%f_として。ただし、実際には、そうでないシェル/プラットフォームはありません。

  • 丸め誤差とオーバーフロー

    • 説明したようにnumfmtprintfを使用すると、丸め誤差が発生するラウンドトリップ変換が発生します(文字列->数値->文字列)。言い換えると、数字のグループ化を使用して再フォーマットすると、異なる数値になる可能性があります。
    • フォーマット文字fを使用して IEEE-754倍精度浮動小数点値 、最大15 有効数字のみ=(小数点の位置に関係なく数字)は正確に保存されることが保証されています(ただし、特定の数字の場合は、より多くの数字で機能する可能性があります) )。 実際には、numfmt[〜#〜] gnu [〜#〜]printfmoreを正確に処理できますそれより;下記参照。誰かがその方法と理由を知っているなら、私に知らせてください。
    • 有効数字が多すぎるか、値が大きすぎると、の動作は一般にnumfmtprintfで異なります、およびプラットフォーム間での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
_

対照的に、大きすぎる数はデフォルトでエラーを生成します

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
_

Linuxバージョンはオーバーフローしているようには見えませんが、BSD/macOSバージョンは数値が大きすぎるとエラーを報告します。


Bashシェル関数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
}
_
8
mklement0