BASH Tips&Trick #000D: Нули начинают и проигрывают истинным bash-маньякам

BASH
Нули выбиваются в лидеры. Так бывает часто в этой жизни, потому что мир полон несправедливости © К.О.
И с этим, безусловно, необходимо бороться как минимум ради достижения всеобщей гармонии.
Почему достижение подобного рода гармонии так важно в BASH? Потому что в его вольной интерпретации все переменные — это строки если не указано иное. Таким образом, вы не можете просто положиться на то, что в качестве параметра вам придёт долгожданное число, а не абракадабра или строка, выглядящая как число, но с миллионом-другим нулей в начале.
Проверить, пришло ли нам число или же нечто несуразное можно множеством способов (TIMTOWTDI в терминологии создателей Perl'а :)). Я, например, предпочитаю делать так:

if ! [[ ${d//[0-9.]/} ]]; then
 echo 'It is a digit'
fi
# OR:
if [[ $d =~ ^[0-9]+$ ]]; then
 echo 'It is a digit'
fi

Отсюда, собственно, напрашивается первый, очевидный метод борьбы с лидирующими нулями: можно проверить переменную на принадлежность к числовому множеству и заодно получить её в пригодном для дальнейших вычислений «обезнуленном» виде, применив хитромудрое регулярное выражение:

if [[ $d =~ ^0*(0$|[1-9][0-9]*)$ ]]; then
 $d=${BASH_REMATCH[1]}
else
 echo 'This is not digit, youre gonna do something nasty'
fi

Недостаток этого метода, возможно, не вполне очевиден, но, как это часто бывает с сусликами и недостатками, «тем не менее он есть» и заключается в том, что постоянная работа с регулярными выражениями замедляет скрипт, и это становится особенно заметным в циклах со значительным количеством итераций. К тому же, паранойя относительно содержимого входных параметров далеко не всегда оправдана, и во многих случаях можно ограничиться просто отсеканием лидирующих нулей у числа, не заморачиваясь над вечным вопросом о том, а число ли это.
Вспомним о том, что, для «отрезания» чего-либо в начале переменной в BASH используется конструкции вида ${VAR#что-либо} и ${VAR##что-либо}, причём отличаются они тем, что в случае с одинарной решёткой осуществляется «нежадный разбор» шаблонных выражений, а в случае с двойной — «жадный».
Сравните:

DIR='/var/cache/apt'
echo ${DIR##*/}
echo ${DIR#*/}

Давайте теперь разберёмся, что нам нужно «отрезать» для решения задачи с лидирующими нулями? Это должны быть все нули в начале строки вплоть до «ненулевого» стоп-символа. Очевидной проблемой здесь является то, что внутренние конструкции BASH по умолчанию используют примитивные шаблоны, в которых либо литерально указываются конкретные символы в нужном количестве (а в общем случае лидирующих нулей может быть сколько угодно), либо — всем давно набивший оскомину «астериск», который в данном случае соответсвует произвольному количеству любых символов.
На наше счастье в шаблонах BASH всё-таки можно использовать конструкции посложнее, для этого достаточно просто сказать:

shopt -s extglob

После этого нам станут доступны конструкции вида *(SEQ) и +(SEQ), аналогичные (SEQ)+ и (SEQ)* в обычных регулярных выражениях.
Вооружившись этим сакральным знанием, удалим-таки лидирующие нули красивым и элегантным способом:

shopt -s extglob
....
d=${d##*(0)}

Этот код очень быстрый и куда менее громоздкий, нежели код, использующий регулярные выражения. Но есть один момент, который прежде мы предпочитали обходить стороной: есть одно единственное число, которое «состоит» из лидирующих нулей — это, собственно, его величество нуль и есть. И после предложенной «обработки» от такого числа не останется ни рожек, ни ножек, вообще ничего, а это совсем не тот результат, который мы ожидали получить.
Неужели принцип TIMTOWTDI в данном случае не действует и нам не солоно хлебавши придётся возвращаться к постылым регэкспам? Разумеется нет! Давайте оценим ситуацию: во-первых переменную, объявленную изначально как численную «обнулить» таким образом невозможно, попробуйте и убедитесь в этом сами:

shopt -s extglob
declare -i d=0
d=${d##*(0)}
echo $d

Впрочем, и лидирующих нулей у такой переменной быть не может :)
Если же переменная является строковой, то сам факт её равенства пустой строке легко использовать для «восстановления» нулевого значения при первом же к ней обращении. Например, вот так:

shopt -s extglob
d=0
d=${d##*(0)}
echo ${d:=0}


Итак, сегодня мы узнали о том, что при программирование на BASH даже тривиальную задачу борьбы с выбивающимися в лидеры «нулями» можно решить множеством способов (и я показал только те из них, которые не используют вызовы сторонних утилит наподобие sed и tr), побуждая к здоровому творчеству и развивая вашу фантазию.
Не переключайтесь и до новых встреч в прямом эфире! :)

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.