У меня есть номер 12343423455.23353
. Я хочу отформатировать число с разделителем тысяч. Таким образом, выход будет 12,343,423,455.23353
Форматирование номера в BASH с разделителем тысяч
Ответ 1
$ printf "%'.3f\n" 12345678.901
12,345,678.901
Ответ 2
ТЛ; др
-
Используйте
numfmt
, если доступны утилиты GNU, например, в Linux по умолчанию:-
numfmt --grouping 12343423455.23353 # → 12,343,423,455.23353 in locale en_US
-
-
В противном случае используйте
printf
с флагом'
field, заключенным в функцию оболочки, которая сохраняет количество входных десятичных разрядов (без жесткого кодирования количества выходных десятичных разрядов).-
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")
n=12343423455.23353; (f=${n#*[.,]} printf "%'.${#f}f\n" "$n")
- Модульный, но несколько неэффективный вариант, который принимает входной номер через stdin (и поэтому может также использоваться с конвейерным вводом):
-
Кроме того, рассмотреть вопрос об использовании моего Linux/MacOS
grp
CLI (устанавливаемая сnpm install -g grp-cli
):-
grp -n 12343423455.23353
-
Во всех случаях есть предостережения; увидеть ниже.
Ответ Игнасио Васкеса-Абрамса содержит решающий указатель для использования с printf
: флаг '
field '
(после %
) форматирует число с разделителем активной локали тысяч:
- Обратите внимание, что
man printf
(man 1 printf
) сам по себе не содержит эту информацию: встроенная утилитаprintf
конечном итоге вызывает библиотечную функциюprintf()
, и толькоman 3 printf
выдает полную картинку относительно поддерживаемых форматов. - Переменные среды
LC_NUMERIC
и, косвенно,LANG
илиLC_ALL
управляют активнойLC_ALL
в отношении форматирования чисел. - И
numfmt
иprintf
относятся к активной локали, как по отношению к разделителю тысяч, так и к десятичной метке ("десятичная точка"). - Использование только
printf
само по себе, как в ответе Ignacio, требует жесткого кодирования количества выходных десятичных разрядов, а не сохранения скольких десятичных разрядов у входных данных; это ограничение, которое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'
- ОК: введенный номер не сгруппирован и использует литовский десятичный знак (запятую).
-
-
Переносимость: POSIX не требует, чтобы утилита
printf
(в отличие отprintf()
библиотеки Cprintf()
) поддерживала символы формата с плавающей запятой, такие как%f
, учитывая, что оболочки POSIX [-like] только для целых чисел; на практике, однако, я не знаю никаких оболочек/платформ, которые этого не делают. -
Ошибки округления и переполнения:
- При использовании
numfmt
иprintf
как описано, происходитnumfmt
преобразование (строка → число → строка), которое подвержено ошибкам округления; другими словами: переформатирование с группировкой цифр может привести к другому номеру. - При использовании символа формата
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/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 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
}