Форматирование чисел в BASH с разделителем тысяч

у меня есть ряд 12343423455.23353. Я хочу отформатировать число с помощью thousand separator. Таким образом, выход будет 12,343,423,455.23353

2 ответов


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

tl; dr

  • использовать 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(), который также поддерживает несколько ввода цифр.
  • ad-hoc альтернативы с участием подрешеток это сохранить количество входных десятичных знаков (предполагается, что входной десятичный знак является либо . или ,):

    • модульный, но несколько неэффективный вариант, который принимает входной номер через 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 / OSX grp Кли (устанавливаемые с npm install -g grp-cli):

    • grp -n 12343423455.23353

во всех случаях есть предостережения; см. ниже.


ответ Игнасио Васкеса-Абрамса содержится критический указатель для использования с printf: the ' флаг поля (после %) форматирует число с разделителем тысяч активных локалей:

  • обратите внимание, что man printf (man 1 printf) не содержит эту информацию сама по себе:утилиты / shell builtin printf в конечном счете называет функции библиотеки printf(), и только man 3 printf дает полную картину относительно поддерживаемых форматов.
  • переменные среды LC_NUMERIC и, косвенно, LANG или LC_ALL управление активным языковым стандартом в отношении форматирования чисел.
  • и numfmt и printf уважать активную локаль, как в отношении разделитель тысяч и десятичная метка ("десятичная точка").
  • используя printf сам по себе, как и в ответе Игнасио, требует, чтобы вы код число выход десятичные знаки, а не сохранение, сколько десятичных знаков имеет вход; это ограничение, что groupDigits() ниже преодолевает.
  • printf "%'.<numDecPlaces>f" имеет одно преимущество над numfmt --grouping, однако:
    • numfmt только decimal числа, в то время как printf ' s %f принимает шестнадцатеричное целых чисел (например, 0x3e8) и десятичные числа научная нотация (например, 1e3).

предостережения

  • локали без группировки: некоторые места, в частности C и POSIX, по определению не применяется группировка, поэтому используйте ' не имеет никакого эффекта в этом событие.

  • реальные нестыковки язык на разных платформах:

    • (LC_ALL='de_DE.UTF-8'; printf "%'.1f\n" 1000) # SHOULD yield: 1.000,1
    • Linux доходность 1.000,1, как ожидалось.
    • OSX / BSD: неожиданно дает 1000,1 - без группирования(!).
  • ввод кол-формате: когда вы передаете номер numfmt или printf, это:
    • не уже содержат группировку цифр
    • должны уже использовать активный десятичная метка локали
    • например:
      • (LC_ALL='lt_LT.UTF-8'; printf "%'.1f\n" 1000,1) # -> '1 000,1'
      • OK: входной номер не сгруппирован и использует литовскую десятичную метку (запятую).
  • мобильность: POSIX не требуются the printf утилиты (в отличие от C printf() функции библиотеки) для поддержки символов формата с плавающей запятой, таких как %f, учитывая, что оболочки POSIX[-like] являются целочисленными; на практике, однако, я не знаю никаких оболочек/платформ, которые этого не делают.

  • ошибки округления и переполнения:

    • при использовании numfmt и printf как описано, происходит преобразование туда и обратно (строка - > номер - > String), который подвержен ошибкам округления; другими словами: переформатирование с группировкой цифр может привести к другому числу.
    • символьном формате 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

напротив, число, которое слишком велико тут генерировать ошибки по умолчанию.

printf:

Linux printf обрабатывает до 20 значащих цифр точно, тогда как реализация BSD / OSX ограничена 17:

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

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

версия Linux никогда не переполняется, в то время как версия BSD / OSX сообщает об ошибке с номерами, которые слишком велики.


функция оболочки 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
}