Форматирование чисел в BASH с разделителем тысяч - PullRequest
16 голосов
/ 21 февраля 2012

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

Ответы [ 2 ]

34 голосов
/ 21 февраля 2012
$ printf "%'.3f\n" 12345678.901
12,345,678.901
8 голосов
/ 12 июня 2015

tl; др

  • Используйте numfmt, если доступны утилиты GNU , например, на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 / macOS grp CLI (устанавливается с npm install -g grp-cli):

    • grp -n 12343423455.23353

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


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

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

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

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

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

    • (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 [-подобные] оболочки являются только целочисленными;на практике, однако, я не знаю никаких оболочек / платформ, которые этого не делают.

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

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