Форматирование чисел в BASH с разделителем тысяч
у меня есть ряд 12343423455.23353
. Я хочу отформатировать число с помощью thousand separator. Таким образом, выход будет 12,343,423,455.23353
2 ответов
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")
- модульный, но несколько неэффективный вариант, который принимает входной номер через stdin (и поэтому также может использоваться с ввод газопровода):
-
кроме того, рассмотрите использование моего Linux / OSX
grp
Кли (устанавливаемые сnpm install -g grp-cli
):grp -n 12343423455.23353
во всех случаях есть предостережения; см. ниже.
ответ Игнасио Васкеса-Абрамса содержится критический указатель для использования с printf
: the '
флаг поля (после %
) форматирует число с разделителем тысяч активных локалей:
- обратите внимание, что
man printf
(man 1 printf
) не содержит эту информацию сама по себе:утилиты / shell builtinprintf
в конечном счете называет функции библиотеки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
утилиты (в отличие от Cprintf()
функции библиотеки) для поддержки символов формата с плавающей запятой, таких как%f
, учитывая, что оболочки POSIX[-like] являются целочисленными; на практике, однако, я не знаю никаких оболочек/платформ, которые этого не делают.-
ошибки округления и переполнения:
- при использовании
numfmt
иprintf
как описано, происходит преобразование туда и обратно (строка - > номер - > String), который подвержен ошибкам округления; другими словами: переформатирование с группировкой цифр может привести к другому числу. - символьном формате
f
использовать значения с плавающей запятой двойной точности IEEE-754, только до 15 значащих цифр (цифры независимо от расположения десятичной метки) являются гарантированный быть точно сохраненным (хотя для определенных чисел это может работать с большим количеством цифр). на практикеnumfmt
и GNUprintf
может точно обрабатывать больше чем это; видите ниже. Если кто знает, как и почему, дайте мне знать. - со слишком большим количеством значащих цифр или слишком большим значением,поведение отличается между
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
}