Вы удивитесь, когда узнаете, почему так важно уметь программировать в bash. На это есть ряд причин:
Обратите внимание — вы уже работаете в bash. Даже если заменена стандартная оболочка, bash по прежнему остается в системе и широко в ней используется, т. к. она является стандартной в Linux. Благодаря этому, сценарий bash не займет много памяти, поскольку будет разделять её с процессом оболочки. Зачем использовать дополнительный интерпретатор (который «съест» 500 КБ памяти), если существует bash, которая уже запущена и способна выполнить поставленную задачу?
Но bash не только постоянно запущена, но и используется практически ежедневно! Поэтому имеет смысл узнать, как использовать её с максимальной эффективностью, сделав работу с bash более приятной и продуктивной. Но зачем изучать программирование в bash? А все потому, что вы уже знакомы с командами, копированием файлов, использованием конвейеров и перенаправлением вывода. Так почему же не выучить язык, который позволит вам использовать эти мощные конструкции более эффективно, раз уж вы с ними знакомы? Командные оболочки раскрывают потенциал систем UNIX, а bash, в свою очередь, оболочка Linux. Она является высокоуровневой прослойкой между вами и компьютером. Повысив свои знания о bash, вы автоматически повысите производительность работы в Linux и UNIX, к тому же это так просто!
Неверный подход в изучении bash может оказаться весьма запутанным делом. Многие
новички, набрав
Хоть это и может поумерить пыл начинающих, но стандартная документация bash не является учебником и рассчитана на тех, кто уже знаком с основами программирования в оболочке. Несомненно, в справочной системе содержится много прекрасной технической информации, но она вряд ли поможет новичкам.
Как раз этому посвящен этот цикл статей. В нем показано на практике, как использовать программные конструкции bash в создании собственных сценариев. Вместо технических деталей, понятным языком объяснены не только принципы работы, но и область применения этих конструкций. К концу третьей статьи, вы сможете спокойно создавать bash сценарии, а также дополнить свои знания чтением, а главное пониманием, стандартной документации bash. Давайте начнем.
В bash, как и в других оболочках, пользователь может определять переменные
среды, которые хранятся внутри неё в виде ASCII строк. Одним из наиболее
удобных свойств переменных среды является то, что они являются неотъемлимой
частью модели процессов UNIX. Это означает, что переменные среды могут
использоваться не только в сценариях оболочки, но и в обычных программах. Когда
мы «экспортируем» переменную из bash, все запущенные впоследствии
программы смогут её использовать несмотря на то, что она определена сценарием
оболочки. Возьмём, к примеру, команду
Стандартный способ объявления переменной среды в bash выглядит так:
$ myvar='This is my environment variable!'
Эта команда объявляет переменную среды под именем «myvar» и содержит строку «This is my environment variable!». Стоит заметить следующее: во-первых, нет пробелов по обе стороны от «=»; поскольку это приведет к ошибке (можете проверить). Во-вторых, можно обойтись и без кавычек, если наша переменная содержит одно слово (включая пробелы или знаки табуляции).
В-третьих, хоть мы и можем использовать как апостроф, так и двойные кавычки, использование двойных кавычек в приведенном примере приведёт к ошибке. Почему? А потому, что использование апострофа отключает возможность bash делать подстановки, которые заключаются в замене специальных символов и последовательностей на определенные значения. Например, символ «!» — символ подстановки истории команд, который bash обычно заменяет на предыдущую набранную команду. (Мы не будем останавливаться на подстановке истории команд, поскольку она редко используется в bash программировании. Дополнительную информацию вы можете найти в разделе «HISTORY EXPANSION» справочной страницы bash.) Хотя эта макро-функциональность и удобна, но сейчас нас больше интересует восклицательный знак в конце значения нашей переменной, чем макрос.
Теперь давайте посмотрим, как использовать переменные среды. Например:
$ echo $myvar This is my environment variable!
Поставив $ перед именем переменной, мы говорим bash заменить её на значение myvar. В терминологии bash это называется «подстановкой переменных». Ну а что произойдет, если мы сделаем следующее:
$ echo foo$myvarbar foo
Мы хотели получить «fooThis is my environment variable!bar», но у нас не вышло. Что же произошло? Короче говоря, мы ввели bash в заблуждение. То есть не возможно понять какую переменную мы имели в виду — $m, $my, $myvar, $myvarbar и т.д. Как мы можем более ясно и четко сказать bash, какую переменную мы хотим получить? Попробуйте:
$ echo foo${myvar}bar fooThis is my environment variable!bar
Как видите, мы можем заключить переменную в фигурные скобки, когда её трудно выделить из остального текста. В то время как использование $myvar быстрее и удобней, ${myvar} работает более предсказуемо. Но по функциональности они не отличаются, поэтому будем использовать обе формы в дальнейшем. Просто запомните, что следует использовать более строгую форму представления в фигурных скобках тогда, когда переменная не отделена от окружающего текста пробелами или знаками табуляции.
Мы уже упоминали возможность «экспортирования» переменных. Когда экспортируется переменная среды, она автоматически становится доступной всем запущенным впоследствии сценариям и программам. Сценарии оболочки могут «добраться» до переменной среды, используя встроенную поддержку, в то время как программы написанные на C должны использовать функцию getenv(). Ниже приводится пример C кода, который поможет нам разобраться в использовании переменных среды с точки зрения C:
#include <stdio.h> #include <stdlib.h> int main(void) { char *myenvvar=getenv("EDITOR"); printf("The editor environment variable is set to %s\n",myenvvar); }
Сохраните приведенный выше код в файл
$ gcc myenv.c -o myenv
Теперь в каталоге появится исполняемая программа, которая при запуске будет
выводить значение переменной среды
$ ./myenv The editor environment variable is set to (null)
Странно... поскольку переменная
$ EDITOR=xemacs $ ./myenv The editor environment variable is set to (null)
Мы ожидали, что myenv содержит значение "xemacs", но опять ошиблись, поскольку
не экспортировали переменную среды
$ export EDITOR $ ./myenv The editor environment variable is set to xemacs
Итак, мы убедились на примере, что другой процесс (в нашем случае программа на C) не видит переменную среды до экспортирования. Стоит заметить, что можно объявить и экспортировать переменную среды одной строкой, а именно:
$ export EDITOR=xemacs
Это работает аналогично двустрочному варианту. Теперь самое время показать, как
уничтожить переменную среды используя
$ unset EDITOR $ ./myenv The editor environment variable is set to (null)
Разделением строк — как следует из названия, является представление
первоначальной строки в виде меньших, отдельных сегментов — и это одна
из задач, которую выполняют большинство сценариев оболочки. Сценариям постоянно
приходиться получать полный путь, и выделять из него завершающий файл или
каталог. Хоть и возможно (причем, достаточно легко!) реализовать это в bash,
но стандартная UNIX-программа
$ basename /usr/local/share/doc/foo/foo.txt foo.txt $ basename /usr/home/drobbins drobbins
$ dirname /usr/local/share/doc/foo/foo.txt /usr/local/share/doc/foo $ dirname /usr/home/drobbins/ /usr/home
Еще одна удобная возможность, которую нужно знать — создание переменной среды, которая содержит в себе выполняемую команду. Это очень легко сделать:
$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt` $ echo $MYDIR /usr/local/share/doc/foo
Проделанное выше называется
$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt) $ echo $MYDIR /usr/local/share/doc/foo
Как видите, bash предоставляет множество способов реализации одних и тех же
вещей. Используя подстановку команд, мы можем поместить любую команду или
конвейер команд между
$ MYFILES=$(ls /etc | grep pa) $ echo $MYFILES pam.d passwd
Безусловно
$ MYVAR=foodforthought.jpg $ echo ${MYVAR##*fo} rthought.jpg $ echo ${MYVAR#*fo} odforthought.jpg
В первом примере мы набрали ${MYVAR##*fo}. И что же это означает? В принципе,
внутри ${ }, мы поместили имя переменной, два ## и шаблон («*fo»).
Затем, bash взяла переменную
f fo СОВПАДЕНИЕ *fo foo food foodf foodfo СОВПАДЕНИЕ *fo foodfor foodfort foodforth foodfortho foodforthou foodforthoug foodforthought foodforthought.j foodforthought.jp foodforthought.jpg
Проверив все строки на совпадение, bash нашла две. Она выделила самую длинную подстроку, убрала её из первоначальной строки и вернула результат.
Второй вид подстановки переменных совпадает с первым, за исключением того, что используется одиночный символ «#». Bash выполняет практически те же действия — она проверяет тот же набор подстрок, что и в первом примере, но убирает не самую длинную, а самую короткую подстроку из первоначальной строки, и возвращает результат. Поэтому, как только она обнаружит «fo» подстроку, она уберёт её из строки и вернет «odforthought.jpg»
Всё это может казаться запутанным, поэтому я покажу Вам простое правило, чтобы
запоминать, какие символы что делают. Когда ищете самое длинное вхождение,
используйте ## (поскольку ## длиннее, чем #). Когда ищете самую короткую
подстроку — используйте #. Как видите, совсем не трудно запомнить! Хотя
постойте, а как запомнить то, что использование символа «#»
означает удаление с
$ MYFOO="chickensoup.tar.gz" $ echo ${MYFOO%%.*} chickensoup $ echo ${MYFOO%.*} chickensoup.tar
Как видите, опции подстановки переменных % и %% работают идентично # и ##, за исключением того, что они удаляют совпавшую подстроку с конца строки. Заметьте, что Вам не нужно использовать символ «*» при удалении определенной подстроки с конца:
MYFOOD="chickensoup" $ echo ${MYFOOD%%soup} chicken
В этом примере не имеет значения, что использовать — «%%» или «%», поскольку только одно совпадение возможно. И запомните: если запутаетесь в значениях «#» и «%», то просто посмотрите на клавиши 3, 4, 5 и сразу все поймете.
Можно использовать еще один вид подстановки переменных для выбора конкретной подстроки, основанный на выборе смещения относительно определенного символа и длины. Попробуйте набрать в bash следующие строки:
$ EXCLAIM=cowabunga $ echo ${EXCLAIM:0:3} cow $ echo ${EXCLAIM:3:7} abunga
Этот способ разделения строки достаточно удобен, нужно всего лишь определить начальный символ и длину подстроки, разделяя всё двоеточиями.
Теперь, когда мы все знаем о разделении строк, давайте напишем простой сценарий оболочки. Он будет получать в качестве аргумента имя файла и выводить, является ли этот файл архивом. Для определения этого, надо искать шаблон «.tar» в конце файла. Вот он:
#!/bin/bash if [ "${1##*.}" = "tar" ] then echo This appears to be a tarball. else echo At first glance, this does not appear to be a tarball. fi
Чтобы получить этот сценарий, сохраните его в файле с именем
$ ./mytar.sh thisfile.tar This appears to be a tarball. $ ./mytar.sh thatfile.gz At first glance, this does not appear to be a tarball.
Хоть он и работает, но все же ему не хватает функциональности. Однако, перед тем, как мы сделаем его более полезным, давайте посмотрим на выражение «if», использованное выше. Внутри него располагается логическое выражение. В bash оператор сравнения «=» проверяет строки на равенство. Все логические выражения в bash заключаются в квадратные скобки. Но что же в нашем примере проверяет выражение? Давайте посмотрим на его левую часть. В соответствии с тем, что мы знаем о разделении строк, «${1##*.}» отделит самое длинное вхождение, совпадающее с «*.» от начала строки, содержащейся в переменной «1», и вернет результат. Таким образом, все что находится за последним символом "." в имени файла, будет возвращено. Очевидно, что если файл заканчивается на «.tar», мы получим «tar» в качестве результата, и условие будет истинным.
Вас может смутить происхождение переменной «1» в самом начале выражения. Однако, все очень просто — $1 является первым аргументом сценария, $2 — вторым и т.д. Что ж, разобравшись с логическим выражением, мы можем рассмотреть конструкцию «if».
Как и в большинстве языков, в bash есть собственная форма представления условных выражений. Когда будете использовать её, придерживайтесь формата, приведённого выше. То есть ставьте «if» и «then» на разные строки, а «else» и завершающий (и обязательный) «fi» горизонтально выровненными с ними. Это делает код более удобным для чтения и отладки. В дополнение к конструкции «if,else», есть еще несколько видов выражения «if»:
if [ condition ] then action fi
Здесь действие action будет выполнено только в том случае, если условие истинно. В противном случае ничего не будет сделано, а выполнение сценария продолжится со строки, следующей за «fi».
if [ condition ] then action elif [ condition2 ] then action2 . . . elif [ condition3 ] then else actionx fi
Приведенная выше форма «elif» будет последовательно проверять каждое условие и выполнит действие, соответствующее первому истинному выражению. Если таковых не окажется, она запустит действие, находящееся в else, если таковое имеется, а затем продолжит выполнение со строки, следующей после всего выражения «if,elif,else».
Теперь, когда мы рассмотрели базовую функциональность bash, самое время собраться с силами и быть готовыми создать несколько настоящих сценариев. В следующей статье будут объяснены циклы, функции, пространства имен и другие важные темы. Тогда мы будем готовы написать более сложные вещи. В третьей статье мы сконцентрируемся на достаточно сложных сценариях и функциях, а также на некоторых вариантах разработки в bash. До встречи!