Официальная возможность получить лицензионный софт бесплатно.
Giveaway of the Day
Это не реклама!

Щелкните для получения прогноза по Биробиджану


среда, 2 сентября 2015 г.

Особенности арифметики в bash: системы счисления

Краткое содержание: надо учитывать или явно указывать систему счисления.

Простая задача: найти, какой месяц был 7 месяцев назад.
Любым способом получаем текущий месяц, например:

(дальше использованы фрагменты рабочего скрипта, поэтому много излишеств, удалены некоторые другие команды, которые этими излишествами активно используются)

# лишняя переменная tSA только для удобства, чтобы не появились ошибки из-за
# неправильного экранирования вложенных команд
tSA=`date +%s`
# сегодня
todaySA=`date -d @$tSA +%Y%m%d`
# месяц сегодня
motSA=${todaySA:4:2}


Если месяц меньше 10, то в переменную motSA попадает значение вида 0N, где N - номер месяца с добавленным ведущим нулём. Всё красиво и гладко.Теперь находим искомое:

curmo=$motSA
echo curmo=$curmo
oldermo=$[curmo-7]
echo oldermo=$oldermo


Однако же... На дворе сентябрь, а значит curmo=09. По логике, 7 месяцев назад был февраль. Казалось бы, чего проще:

oldermo=$[curmo-7] ==> oldermo=$[09-7]

Однако, вместо oldermo=2 получаем ошибку:

$ curmo=09;echo $[curmo-7]
bash: 09: значение слишком велико для основания (ошибочная метка "09")


Мать моя женщина! Что за нафиг? Роемся в памяти и вспоминаем, что в некоторых языках программирования число с ведущим нулём считается заданным не в десятичной системе. В каком-то из ассемблеров ведущий ноль указывал на восьмеричную систему счисления (СС), и там вылезла бы подобная ошибка, потому как восьмеричные цифры от нуля до семёрки.
Начинаем курить мануал по bash и без проблем находим упоминание про base, hexadecimal, octal и т.п. В языке FORTH есть специальная конструкция, позволяющая временно изменить основание системы счисления (ОСС)в пределах текущего (под)выражения, что-то подобное должно быть и в bash. Точно, есть.

Для указания СС, в которой записано число, перед этим числом ставим префикс, в котором указываем десятичное значение ОСС. Например, 16 для шестнадцатиричной:

$ a=$[16#20];echo $a; a=$[8#20];echo $a;
32
16


По умолчанию bash распознает восьмеричную и шестнадцатеричную СС по префиксам 0 и 0x соответственно. Есть и некоторые другие особенности (знак "меньше" в сочетании "greater< than 9" не мой - скопировано из мануала к bash 4.3.11, для версии 4.2.25 такой ошибки нет):

Constants  with  a  leading 0 are interpreted as octal numbers.  A leading 0x or 0X denotes hexadecimal.  Otherwise, numbers take the form [base#]n, where the optional base is a decimal number between 2 and 64 representing the arithmetic base, and n is a number in that base.  If base# is omitted,  then  base 10  is  used.  When specifying n, the digits greater< than 9 are represented by the lowercase letters, the uppercase letters, @, and _, in that order.  If base is less than or equal to 36, lowercase and uppercase letters may be used interchangeably to represent numbers between 10 and 35.

Ладно, с ОСС разобрались, тестовый пример работает:

$ echo $[10#09-7]
2


И тут снова вылезает злосчастное "однако":

$ curmo=09;echo $[10#curmo-7]
bash: 10#curmo: значение слишком велико для основания (ошибочная метка "10#curmo")


На этот раз ошибка в том, что с указанием ОСС имя переменной перестает восприниматься как имя, а считается просто числом, записанным в указанной СС. Но в десятичке нет цифр c, m, o, r или u. Придется разыменовывать переменную:

$ curmo=09;echo $[10#$curmo-7]
2


Ура!

Комментариев нет:

Отправить комментарий

Пожалуйста, воздержитесь от грубостей и персональных нападок.
Я не против матерщины, но она должна быть уместной и использоваться для выражения эмоций, а не в качестве основного средства выражения мыслей.