Готовый перевод Bash by example / Bash в примерах: Part 1: Fundamental programming in the Bourne again shell (bash)

Внимание! Этот перевод, возможно, ещё не готов, так как модераторы установили для него статус «перевод редактируется»

Введение

Вы, может быть, зададитесь вопросом (удивитесь), почему вы должны изучать программирование в Bash. Что ж, здесь есть пара убедительных причин:

Вы уже запустили его

Если вы проверите, то вероятно обнаружите, что вы уже запустили bash (прямо сейчас). Даже если вы изменили свою оболочку по умолчанию, bash наверное по-прежнему будет работать в вашей системе, так как это стандартная оболочка Linux и используется для различных целей. Поскольку bash уже загружен, любые дополнительные bash-сценарии, которые вы запустите, будут изначально эффективно использовать память, потому что они используют память совместно с любыми другими уже запущенными bash-процессами. Почему загрузка 500 килобайтного интерпретатора, если вы уже запустили что-либо, будет делать работу, и сделает это хорошо?

Выуже используете его

Вы не только уже используете его, но и ежедневно взаимодействуете с bash. И это всегда так, поэтому важно узнать как использовать его потенциал полностью. Это сделает вашу работу с bash более приятной и плодотворной. Но почему вы должны освоить программирование в bash? Просто потому, что вы уже думаете о запуске команд, копировании файлов, перенаправлении потока вывода. Не должны ли вы изучить язык, позволяющий вам использовать мощные и экономящие время конструкции, которые вы уже знаете как использовать? Командные оболочки раскрывают потенциал UNIX-систем, а bash - это командная оболочка в Linux. Это связь между вами и машиной на высоком уровне. Совершенствуя ваши знания о bash, вы одновременно повысите продуктивность своей работы с Linux и UNIX -- это так просто.

Путаница в bash

Неправильное изучение bash может сильно запутать. Многие новички открывают man, чтобы прочитать руководство пользователя bash, и сталкиваются только лишь с кратким техническим описанием функций оболочки. Другие открывают info (чтобы посмотреть докумнетацию GNU) и получают ту же самую страницу man или (если повезет) только слегка упрощенную её версию.

Как это неутешительно для новичков, но стандартная документация bash не может быть одинаково полезна для всех и ориентирована прежде всего на тех, кто уже в целом знаком с программированием в командной оболочке. В man определенно много отличной технической информации, но её полезность для начинающих ограничена.

Вот для чего предназначено это руководство. Здесь я покажу вам как нужно программировать в bash, вы научитесь писать свои собственные скрипты. В отличии от технических описаний, я буду объяснять на простом языке так, что вы будете не только знать как что-то работает, но и когда вы должны это использовать. К концу этого руководства из трех частей вы сможете писать свои замысловатые скрипты в bash и выйдите на уровень, необходимый для комфортного использования bash и пополнения своих знаний путем чтения (и понимания!) стандартной документации bash. Давайте начнем.

Переменные окружения

В bash и почти во всех других оболочках пользователь может определять переменные окружения, которые хранятся в виде ASCII строки. Одно из удобств переменных окружения заключается в том, что они являются стандартной частью модели процессов UNIX. Это свойство переменных окружения не ограничивается только сценариями оболочки, а также может быть использовано обычными скомпилированными программами. Когда мы "экспортируем" переменную окружения в bash, любые программы, которые мы запустили позже, могут читать нашу настройку, будь то сценарий оболочки или нет. Хорошим примером является команда vipw, которая позволяет супер-пользователю редактировать системный файл паролей. Установив в переменную окружения EDITOR имя вашего любимого текстового редактора, вы можете настроить vipw использовать его вместо vi, это удобно, если вы используете xemacs и действительно не любите vi.

Стандартный способ определения переменной окружения:

$ myvar='This is my environment variable!'

Приведенная выше команда определяет переменную окружения с названием "myvar", содержащую в себе строку "This is my environment variable!". Здесь есть несколько вещей, на которые следует обратить внимание: во-первых, не должно быть пробелов с обеих сторон от знака "="; любой пробел приведет к ошибке (попробуйте это и увидите сами). Во-вторых, стоит отметить то, что мы можем избавиться от кавычек, если определяем одно слово, но они необходимы, когда значение переменной состоит более чем из одного слова (содержит пробелы и табуляции).

Примечание

За подробной информации о том, как должны быть использованы кавычки, вы можете обратиться в раздел "QUOTING" на man-странице bash. Существование специальных последовательностей символов, которые "подставляются" (заменяются) другими значениями усложняет обработку строк в bash. В этом руководстве мы покроем только наиболее часто используемые функции кавычек.

Далее, хотя мы можем использовать двойные кавычки вместо одинарных, в предыдущем примере это привело бы к ошибке. Почему? Потому что одинарные кавычки отключают функцию bash называемую подстановкой, когда специальные символы и последовательности символов заменяются своими значениями. Например, "!" -- это символ, который bash обычно заменяет командой, напечатанной перед этим. (Мы не будем покрывать это в данных статьях, так как это редко используется при программировании в bash. Для подробной информации об этом смотрите раздел "HISTORY EXPANSION" на man-странице 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
#include

int main(void) {
char *myenvvar=getenv("EDITOR");
printf("The editor environment variable is set to %s
",myenvvar);
}

Сохраните приведенный выше код в файл с названием myenv.c и затем скомпилируйте его с помощью команды:

$ gcc myenv.c -o myenv

Так в текущей директории мы получим исполняемый файл, который после запуска напечатает значение переменной окружения EDITOR, если таковая установлена. Вот что получается, когда я запускаю его на своем компьютере:

$ ./myenv
The editor environment variable is set to (null)

Хммм... из-за того, что переменная окружения EDITOR не была установлена, программа получает пустую строку. Давайте попробуем присвоить ей специальное значение:

$ EDITOR=xemacs
$ ./myenv
The editor environment variable is set to (null)

Хотя вы вероятно ожидали, что myenv напечатает "xemacs", это не сработало, потому что мы не экспортировали переменную окружения EDITOR. Сейчас мы это исправим:

$ export EDITOR
$ ./myenv
The editor environment variable is set to xemacs

Итак, вы видели собственными глазами, что другой процесс (в данном случае наш пример программы на C) не может видеть переменную окружения до того, как она будет экспортирована. Кстати, если вы хотите, то можете определить и экспортировать переменную, используя одну строку, а именно:

$ export EDITOR=xemacs

Это работает аналогично версии с двумя строками. Теперь самое время, чтобы показать как очистить переменную окружения, используя unset:

$ unset EDITOR
$ ./myenv
The editor environment variable is set to (null)

Обзор разбиения строк

Разбиение строк -- то есть разделение исходной строки на более маленькие, отдельные куски(-ок) -- одна и тех задач, которые выполняются ежедневно вашими рядовыми сценариями оболочки. Многократно сценариям требуется взять полный путь и найти в нем только имя файла или директории. Хотя это возможно (и весело!) написать программу для этого в bash, стандартный исполняемый файл UNIX -- basename делает это очень хорошо.

$ basename /usr/local/share/doc/foo/foo.txt
foo.txt
$ basename /usr/home/drobbins
drobbins

basename довольно удобный инструмент разбиения строк. Его спутник называется "dirname", возвращает "другую" часть пути, которую basename отбрасывает:

$ dirname /usr/local/share/doc/foo/foo.txt
/usr/local/share/doc/foo
$ dirname /usr/home/drobbins/
/usr/home

Примечание

И dirname и basename не смотрят на какие-либо файлы или директории на диске, они исключительно команды обработки строк.

Подстановка команд

Также полезно знать как создать переменную окружения, содержащую в качестве значения результат выполнения команды. Делается это очень просто:

$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)
$ echo $MYDIR
/usr/local/share/doc/foo

То, что мы только что проделали, называется подстановкой команды.

В данном примере следует обратить внимание на несколько моментов.

В первой строке мы просто поместили команду в конструкцию $().

Обратите внимание, что это можно сделать также при помощи обратных кавычек (клавиша, обычно находящаяся над клавишей Tab):

$ 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

Также следует заметить, что конструкция $() предпочтительнее чем в сценариях оболочки по той причине, что она поддерживается большинством оболочек, легче для набора и чтения, а также менее сложна при использовании во вложенных конструкциях, таких как, например:

$ MYFILES=$(ls $(dirname foo/bar/oni))

Разбиваем строки как профессионалы

Несмотря на то, что basename и dirname прекрасные инструменты, порой возникает необходимость в более сложных, чем стандартные операции с путями, "шаманствах" со строками.

Если нам нужно замутить что-то хитрое, мы можем воспользоваться функциональностью, которую предоставляет встроенный в bash механизм подстановки значения переменных.

Мы уже использовали стандартную форму подстановки значения, которая выглядит так: ${MYVAR}. Однако помимо тривиального случая bash предоставляет несколько удобных средств манипуляции со строками.

Взгляните на следующие примеры:

$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg

В первом примере мы напечатали ${MYVAR##*foo}. Давайте разберемся что это значит. Буквально мы напечатали внутри конструкции ${} имя переменной, две решетки и шаблон ("*fo"). Далее, bash взял значение переменной MYVAR, нашел самую длинную подстроку от начала строки "foodforthought.jpg", подходящую под шаблон "*fo" и "отрезал" ее от начала исходной строки. Поначалу трудно сообразить почему так происходит, поэтому давайте пошагово разберемся как отработал спецсимвол "##" в этом примере.

Сначала bash начал перебор всех подстрок с начала исходной строки "foodforthought.jpg", в поисках подходящих под шаблон "*fo".

Вот список всех подстрок, которые были проверены:

f
fo СООТВЕТСТВУЕТ *fo
foo
food
foodf
foodfo СООТВЕТСТВУЕТ *fo
foodfor
foodfort
foodforth
foodfortho
foodforthou
foodforthoug
foodforthought
foodforthought.j
foodforthought.jp
foodforthought.jpg

В результате поиска по шаблону в строке bash нашел два совпадения. Далее выбирается самое длинное из них и удаляется из исходной строки, начиная от ее начала, и возвращается результат после удаления.

Вторая строка из примера выше с подстановками значения переменной идентичная первой, с той разницей, что использован только один символ "#" - и bash проделывает также практически идентичные действия. Он проверяет тот же набор подстрок как и в только что разобранном примере, с той лишь разницей, что удаляется не самая длинна, а самая короткая подстрока, совпадающая с шаблоном. И возвращается результат удаления. То есть как только находится первая подстрока "fo" - она немедленно удаляется из исходной строки и возвращается значение "odtothought.jpg".

Все это может показаться очень запутанным, поэтому я покажу вам простой способ запомнить эту функциональность.

При поиске самой длинной подстроки используйте ## (потому что 2 решетки длиннее, чем одна). Если ищите самую короткую - используйте #. Видите, совсем несложно запомнить!

Погодите, а как запомнить, что нужно использовать '#' для удаления подстроки, начиная от *начала*? Просто! Посмотрите на US раскладку на клавиатуре. Shift+4 - это символ "$", который подставляет значение переменной. Можно заметить, что сразу слева от "$" расположен символ "#". То есть "#" находится "в начале" "$" и, таким образом (согласно данной мнемонике), "#" удаляет символы от начала строки.

Вам, наверное, интересно узнать как удалять символы от конца строки. Если вы догадываетесь, что нужно использовать символ, насположенный сразу справа от "$" в US раскладке ("%") - поздравляю, вы совершенно правы.

А вот несколько коротких примеров того, как можно удалять части строки, начиная от конца:

$ 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

Такая техника работы со строками может оказаться весьма кстати; просто указываем начальный символ и длину подстроки через двоеточия.

Применение работы со строками на практике.

Теперь, когда мы узнали все о разбиении строк, давайте напишем простой маленький shell-сценарий. Наш сценарий будет принимать один файл в качестве аргумента и будет печатать, имеет ли он вид tar-архива. Для определения, что это формат tar, сценарий будет искать по шаблону ".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 и введите команду chmod 775 mytar.sh, чтобы сделать его исполняемым.

$ ./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".

Оператор if

Как и большинство языков программирования bash имеет собственный формe записи условий. При их использовании следует придерживаться вышеупомянутого формата; то есть располагайте "if" и "then" на отдельных строках, а "else" и завершающее ключевое слово "fi" выровняйте с ними по горизонтали. Это позволит легче читать и отлаживать код. В дополнении к формату "if,else" есть несколько других форматов оператора "if":

if [ условие ]
then
команда
fi

Это выражение выполняет команду только, если условие истинно, в противном случае команда игнорируется, и выполняются строки, следующие за "fi".

if [ условие ]
then
команда
elif [ условие2 ]
then
команда2
.
.
.
elif [ условие3 ]
then

else
командаx
fi

Блок "elif" будет последовательно проверять каждое условие и выполнит команду, соответствующую первому истинному условию. Если таковых не найдется, то выполнится команда из блока "else", если он есть, а затем продолжится выполнение строк следующих за всеми блоками "if,elif,else".

В следующий раз

Теперь, когда мы рассмотрели основные возможности bash, пришло время, чтобы поднять темпы и приготовиться к написанию нескольких настоящих сценариев. В следующей статье я рассказу о циклических конструкциях, функциях, пространстве имен и о других важных темах. Тогда мы будем готовы, чтобы писать несколько более сложные сценарии. В третьей статье мы сосредоточимся исключительно на очень сложных сценариях и функциях, а также ... До встречи!

Ресурсы

Полезные ссылки

* Bash в примерах: Часть 2.
* Bash в примерах: Часть 3.
* Посетите домашнюю страницу GNU bash.

Дэниэль Роббинс

Дэниэль Роббинс — основатель сообщества Gentoo и создатель операционной системы Gentoo Linux. Дэниэль проживает в Нью-Мехико со свой женой Мэри и двумя энергичными дочерьми. Он также основатель и глава Funtoo, написал множество технических статей для IBM developerWorks, Intel Developer Services и C/C++ Users Journal.

Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется

Переведено на Нотабеноиде
http://notabenoid.org/book/14299/46834

Переводчики: Daemon786, Clyde, undeceived

Настройки

Готово:

100.00% КП = 1.0