····#·· пользователь не определил другой символ с помощью флага −d.
····integer=$(echo $1 | cut −d. -f1) # Слева от точки
····decimal=$(echo $1 | cut −d. -f2) # Справа от точки
····# Проверить присутствие дробной части в числе.
····if ["$decimal"!= "$1"]; then
······# Дробная часть есть, включить ее в результат.
······result="${DD:= '.'}$decimal"
····fi
··thousands=$integer
··while [$thousands −gt 999]; do
····remainder=$(($thousands % 1000)) # Три последние значимые цифры
····# В 'remainder' должно быть три цифры. Требуется добавить ведущие нули?
····while [${#remainder} −lt 3]; do # Добавить ведущие нули
······remainder="0$remainder"
····done
····result="${TD:=","}${remainder}${result}" # Конструировать справа налево
····thousands=$(($thousands / 1000)) # Оставить остаток, если есть
··done
··nicenum="${thousands}${result}"
··if [! -z $2]; then
····echo $nicenum
··fi
}
DD="." # Десятичная точка для разделения целой и дробной части
TD="," # Разделитель групп разрядов
# Начало основного сценария
# =================
··while getopts "d: t: " opt; do
····case $opt in
······d) DD="$OPTARG";;
······t) TD="$OPTARG";;
····esac
··done
··shift $(($OPTIND — 1))
··# Проверка ввода
··if [$# −eq 0]; then
····echo "Usage: $(basename $0) [-d c] [-t c] number"
····echo " −d specifies the decimal point delimiter"
····echo " −t specifies the thousands delimiter"
····exit 0
··fi
··nicenumber $1 1 # Второй аргумент заставляет nicenumber вывести результат.
··exit 0
Как это работает
Основная работа в этом сценарии выполняется циклом while внутри функции nicenumber() , который последовательно удаляет три младших значащих разряда из числового значения в переменной thousands и присоединяет их к создаваемой форматированной версии числа . Затем цикл уменьшает числовое значение в thousands и повторяет итерацию, если необходимо. Вслед за функцией nicenumber() начинается основная логика сценария. Сначала с помощью getopts , анализируются параметры, переданные в сценарий, и затем вызывается функция nicenumber() с последним аргументом, указанным пользователем.
Запуск сценария
Чтобы опробовать этот сценарий, просто вызовите его с очень большим числом. Сценарий добавит десятичную точку и разделители групп разрядов, использовав значения либо по умолчанию, либо указанные с помощью флагов.
Результат можно внедрить в сообщение, как показано ниже:
echo "Do you really want to pay \$$(nicenumber $price)?"
Результаты
Сценарий nicenumber может также принимать дополнительные параметры. Листинг 1.8 демонстрирует форматирование нескольких чисел с использованием сценария.
Листинг 1.8: Тестирование сценария nicenumber
$ nicenumber 5894625
5,894,625
$ nicenumber 589462532.433
589,462,532.433
$ nicenumber −d, -t. 589462532.433
589.462.532,433
Усовершенствование сценария
В разных странах используют разные символы в качестве десятичной точки и для разделения групп разрядов, поэтому в сценарии предусмотрена возможность передачи дополнительных флагов. Например, в Германии и Италии сценарию следует передать −d"." и −t",", во Франции −d"," и −t " ", а в Швейцарии, где четыре государственных языка, следует использовать −d"." и −t "'". Это отличный пример ситуации, когда гибкость оказывается ценнее жестко определенных значений, потому что инструмент становится полезным для более широкого круга пользователей.
С другой стороны, мы жестко установили, что во входных значениях роль десятичной точки будет играть символ".", то есть, если вы предполагаете использование другого разделителя дробной и целой части во входных значениях, измените символ в двух вызовах команды cut в строках и , где сейчас используется".".
Ниже показано одно из решений:
integer=$(echo $1 | cut −d$DD −f1) # Слева от точки
decimal=$(echo $1 | cut −d$DD −f2) # Справа от точки
Это решение работоспособно, только если разделитель дробной и целой части во входном значении не отличается от разделителя, выбранного для результата, в противном случае сценарий просто не будет работать. Более сложное решение состоит в том, чтобы непосредственно перед этими двумя строками включить проверку, позволяющую убедиться, что разделитель дробной и целой части во входном значении совпадает с разделителем, указанным пользователем. Для реализации проверки можно использовать тот же трюк, что был показан в сценарии № 2: отбросить все цифры и посмотреть, что осталось, например: