Daniel Robbins (drobbins@gentoo.org), президент и главный исполнительный директор, Gentoo Technologies, Inc
Оригинал: Bash by example, Part 1
Дата публикации: 01 Mar 2000
Свободный перевод Татарников Сергей.
Декабрь 2009г.
Об авторе
Проживает в Альбукерке, штат Нью-Мексико. Daniel Robbins является главным архитектором проектов Gentoo, генеральный директор Gentoo Technologies, Inc, наставником для Linux Advanced Multimedia Project (LAMP), и один из авторов книги для Макмиллан Caldera OpenLinux Unleashed, SuSE Linux Unleashed, и Samba Unleashed. Даниэль был связан с компьютерами, в некотором роде со второго класса, когда он впервые изучает язык программирования Лого, а также получает потенциально опасные дозы Pac Man. Вероятно, это объясняет, почему с тех пор он служит в качестве ведущего художника-графика в Sony Electronic Publishing/Psygnosis. Даниэль любит проводить время со своей женой Марией, которая ждет ребенка весной этого года. Вы можете связаться с Даниэлем на drobbins@gentoo.org.
Резюме: Научившись программировать на языке сценариев Bash, день за днем Ваше взаимодействие с Linux буде все более увлекательным и продуктивным, и сможете опираться на стандартные UNIX конструкции (такие как pipe и перенаправления), который мы все знаем и любим. В этой серии из трех частей, Daniel Robbins научит вас, как программировать на Bash на собственном примере.
Вы можете спросить: "Почему я должен научиться Bash-программированию?". Ну, есть пара веских причин:
Вы уже запустили его
Если вы проверите, вы, вероятно, обнаружите, что вы работаете в Bash прямо сейчас. Даже если вы изменили настройки оболочки по умолчанию, Bash, вероятно, еще работает где-то в вашей системе, потому что это стандартная оболочка Linux и используется для различных целей. Поскольку Bash уже запущен, то любые дополнительные скрипты Bash будет запускать по сути в своей памяти, эффективно, поскольку они совместимы с любыми уже запущенными процессами Bash в памяти.
Вы уже используете его
Вы не только уже запустили Bash, но на самом деле Вы взаимодействуете с Bash на каждый день. Он всегда запущен иготов к работе, поэтому имеет смысл узнать, как использовать его в полном объеме. Это позволит сделать ваш опыт работы с Bash более увлекательным и продуктивным. Но почему как же вы узнаете, Bash-программирование? Легко, потому что вы уже думаете, с точки зрения выполнения команд, CPing файлов и pipe и перенаправления вывода и пр.. Если вы выучите язык, это позволит использовать и развивать эти мощные конструкции экономии времени. Командные оболочки раскрывают потенциал системы UNIX, а Bash является оболочкой Linux. Это высокого уровня связующее звеном между Вами и Вашей машиной. Наращивайте в Ваши знания о Bash, и Вы будете автоматически повышать производительность под Linux и UNIX - это так просто.
Запутанность Bash
Обучение Bash может быть очень запутанным процессом. Много новичков набирают "man bash", чтобы просмотреть man-страницу Bash, и видят только очень краткое описание и техническая информация. Другая команда "info bash" (для просмотра документации GNU Info), вызыват либо ту же man-страницу, которая будет повторно показана, либо (если повезет), info-страницу немного с более дружественной информацией.
Хотя это может быть несколько разочаровывающими для новичков, стандартная документации Bash не можете быть всем для всех людей, и ориентирована в на тех, кто уже знакомы с shell-программированием в целом. Там определенно много отличной технической информации, но ограничена полезность для начинающих.
Вот где этот материал будет актуальным. В нем я покажу Вам, как на самом деле использовать Bash конструкции программирования, так что вы сможете писать собственные сценарии. Вместо технических описаний я приведу вам объяснения на простом языке, так что вы будете знать не только то, как что что-то делает, но и когда вы фактически должны ето использовать. К концу этой серии из трех частей, Вы сможете писать ваши собственные сложные сценарии Bash, и быть на уровне, Вы сможете комфортно использовать Bash и дополните свои знания по чтению (и пониманию!) Стандартной документации Баш. Давайте начнем.
Переменные
В Bash, и почти во всех других оболочках, пользователь может определить переменные окружения, которые хранятся в виде строки ASCII. Одна из удобных вещей, то что они являются неотъемлемой частью модели процесса UNIX. Это означает, что переменные среды используются не только в сценариях командной оболочки, но могут быть использованы в стандартных программах. Когда мы делаем "export" переменной среды в Bash, любая последующая программа, которую мы запустим читает наши настройки, будь то скрипт или нет. Хорошим примером является vipw команда, которая обычно позволяет изменить файловую систему паролей. Установив переменную окружения EDITOR с именем вашего любимого текстового редактора, вы можете настроить vipw использовать его вместо vi, удобная вещь, если вы привыкли к XEmacs и по-настоящему не любите vi.
Стандартный способ определить переменную среды Bash это:
$ myvar='Это моя переменную окружения!'
Вышеуказанная команда определена переменная окружения называется "myvar" и содержит строку "Это моя переменная окружения!". Есть несколько вещей, определенных выше: во-первых, нет никаких пробелов вокруг знака "="; любое пространство, приведет к ошибке (попробуйте и Вы увидете). Во-вторых, нужно отметить, что хотя мы могли бы обойтись без кавычек, если бы мы определяли одним словом, но они необходимы, когда значение переменной окружения содержит более чем одно слово (содержит пробелы или символы табуляции). В-третьих, хотя мы обычно можем использовать двойные кавычки вместо одинарных кавычек, если сделать это в приведенном выше примере, то это повлекло бы за собой ошибки. Почему? Поскольку использование одиночных кавычек отключает расширенную функцию Bash, когда действуют специальные символы и последовательности символов заменяются их значениями. Например, "!" симолу соотвествует расширенный символ истории, который обычно Bash заменяет ранее набранной командой. (Мы не будем описывать расширение истории в этой серии статей, потому что это не часто используется в Bash-программировании. Более подробную информацию о нем см. "HISTORY EXPANSION" раздел на man-cтранице Bash.) При том, что как макроопределение может пригодиться, сейчас мы хотим буквально восклицательный знак в конце нашей переменной окружения, а не макроопределение.
Примечание:
Cпецифика маскировки (quoting)
Маскировка (quoting) используется для отмены специального значения для командного интерпретатора определенных символов или слов. Маскировку можно использовать для отмены специфической обработки специальных символов, для предотвращения распознавания зарезервированных слов, а также для предотвращения подстановки параметров. Каждый из метасимволов, имеет специальное значение для командного интерпретатора и должен маскироваться, если используется буквально. Имеется три механизма маскировки: символ маскировки (escape character), одиночные кавычки и двойные кавычки. Незамаскированная обратная косая черта является символом маскировки. Он требует использовать следующий за ним символ (за исключением перевода строки) буквально. Для более подробной информации, как маскировки должны использоваться в Bash, вы можете посмотреть в разделе "QUOTING" на man-странице bash.
Теперь давайте посмотрим, как фактически использовать переменные окружения. Вот пример:
$ echo $myvar
Это моя переменная окружения!
Мы можем попробовать изменить значение myvar. В Bash терминологии это называется "расширение переменных". Что если мы, попробуйте следующее:
$ echo foo$myvarbar
foo
Мы хотели бы получить "fooЭто моя переменная окружения!bar", но это не сработало. Что же произошло? В общем, Bash запутался. Он не смог понять, что мы хотели бы расширить переменную $$m, $my, $myvar или $myvarbar и т.д. Как мы можем более четко и ясно сказать Bash, что переменная, которую мы имеем в виду это $myvar? Попробуйте это:
$ echo foo${myvar}bar
foo
fooЭто моя переменная окружения!bar
Как вы можете видеть, мы можем заключить переменную окружения, в фигурные скобки, и тогда она четко не отделена от окружающего текста. А $myvar быстрее вводить и это будет работать в большинстве случаев, ${myvar} будет корректно обработано практически в любой ситуации. Кроме этого, оба случая делают то же самое, и вы увидите, обе формы расширения переменных в остальной части этой статьи. Помните, что можно не использовать явную конструкцию с фигурными скобками форме, если переменная среды не изолирована от окружающего текста пробелами (пробел или знаки табуляции).
Напомним, что мы также упомянули, что можем сделать "export" переменных. Когда мы экспортируем переменную окружения, оно автоматически становится доступной в любой среде, впоследствии запускаемым скриптам, либо исполняемым файла. В си-программах можно использовать функцию пуеутм(). Вот код примера C, который вы должны набрать и скомпилировать - это позволит нам понять переменные среды с точки зрения C:
myvar.c - пример си-программы, выводящей переменную окружения
#include
#include
int main(void) {
char *myenvvar=getenv("EDITOR");
printf("The editor environment variable is set to %s\n",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
Итак, Вы увидели, что другой процесс (в случае нашего примера си-программа) не видит переменную окружения, пока она не будет экспортирована. Кстати, если хотите, то можете определить и экспортировать переменную одной командой, а именно:
$ export EDITOR=xemacs
Это работает одинаково в обоих вариантах. Теперь посмотрим как удалить переменную окружения:
$ unset EDITOR
$ ./myenv
The editor environment variable is set to (null)
Разделение строк
Разделение строк - то есть, расщепление исходной строки на меньшие, отделить кусок - одна из тех задач, которые часто выполняет ваш сценарий оболочки. Много раз, сценариям оболочки необходимо принять полностью определенный путь, и найти отдельно имя файла или каталога. Хотя это возможно и вручную, но есть стандартные базовые UNIX исполняемые команды и выполняют они это очень хорошо:
$ basename /usr/local/share/doc/foo/foo.txt
foo.txt
$ basename /usr/home/drobbins
drobbins
basename довольно удобный инструмент для отделения имени файла. Его спутник, называется dirname, и возвращает "другую" часть пути без имени файла:
$ dirname /usr/local/share/doc/foo/foo.txt
/usr/local/share/doc/foo
$ dirname /usr/home/drobbins
/usr/home
Примечание:
dirname and basename
И dirname и basename не смотрят на какие-либо файлы или каталоги на диске, они оперируют над строками команд.
Замена команды
Есть очень удобная вещь, чтобы создать переменную окружения, которая содержит результ исполнения команд. Это очень просто сделать:
$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt`
$ echo $MYDIR
/usr/local/share/doc/foo
То что мы сделали называется "замена команды". Несколько вещей стоит заметить, в данном примере. На первой линии, мы просто прилагаются командой мы хотели, чтобы выполнить в спине кавычки. Это не стандартный одиночные кавычки, а вместо этого пришел с клавиатуры ключ, который обычно сидит над клавишей Tab. Мы можем сделать то же самое с альтернативный синтаксис замены команды в Bash:
$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)
$ echo $MYDIR
/usr/local/share/doc/foo
Как вы можете видеть, Bash предоставляет несколько способов выполнить одно и то же. Использование замены команды, мы можем поставить любую команду или трубопровод между командами `или` $ () и присвоить его переменной окружения. Вот пример того, как использовать трубопровод с командой замещения:
MYFILES=$(ls /etc | grep pa)
bash-2.03$ echo $MYFILES
pam.d passwd
Разделение строк по-взрослому
Хотя basename и dirname хорошие инструменты, бывают случаи, когда нам может
понадобиться для выполнения более продвинутые средства разделения строк, чем
просто стандартные манипуляции. Когда мы нуждаемся в большем, мы можем
воспользоваться в Bash передовым встроенным функционалом. Мы уже использовали
стандартный вид расширения переменной, который выглядит примерно так:
$(MYVAR). Но Bash может выполнять некоторые разделения строки так ка Вам
хочется. Взгляните на следующие примеры:
$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg
В первом примере мы ввели ${MYVAR##*fo}. Что это значит? Внутри $() мы ввели имя переменной окружения, два ##, а также шаблон ("*fo"). Этим мы сказали Bash принять MYVAR, найти самую длинную подстроку от начала строки "foodforthought.jpg", которая соответствует шаблону "*fo", и проверить с начала строки. Это немного трудно понять. Чтобы получить представление о том, как эта специальная "##" работает вариант, давайте разберем пошагово как Bash разобрал эту строку. Во-первых, он начал поиск подстроки в начале "foodforthought.jpg", которые соответствуют любому символу "*fo". Вот подстроки, что я проверил:
f
fo MATCHES *fo
foo
food
foodf
foodfo MATCHES *fo
foodfor
foodfort
foodforth
foodfortho
foodforthou
foodforthoug
foodforthought
foodforthought.j
foodforthought.jp
foodforthought.jpg
После поиска подстроки по шаблону, вы можете увидеть, что Bash найдено два совпадения. Он выбирает самый длинное, удаляет его с самого начала исходной строки, и возвращает результат.
Вторая форма является идентичной первой, за исключением того, что используется только один "#" - и Bash выполняет почти идентично первому примеру. Она проверяет и тот же набор подстрок, как и наш первый пример, кроме того, что удаляет Bash кратчайшее совпадение с шаблоном, и возвращает результат. Таким образом, как только она проверяет "fo" подстроку, он удаляет "fo" из нашей строки и возвращает "odforthought.jpg".
Это может показаться весьма загадочными, и я покажу вам простой способ запомнить эту функциональность. При поиске длинного соответствия, используйте ## (## потому что это больше, чем #). В поисках кратчайшего шаблона, используйте #. Смотрите, не так трудно запомнить все! Подождите, как вы помните, что мы должны использовать символ '#', чтобы удалить из начала строки? Это просто! Вы заметите, что на клавиатуре Shift-4 - "$", которая является в Bash обозначением переменной. На клавиатуре, сразу слева от "$" находится "#". Таким образом, вы можете видеть, что "#" это начало "$", и таким образом, "#" удаляет символы от начала строки. Вы можете спросить, как удалить символы в конце строки. Если вы уже догадались, что мы используем клавишу сразу справа от "$" ("%"), вы правы! Вот несколько быстрых примеров того, как отрубать заднюю часть строки:
$ 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" в конце файла. Вот этот скрипт:
mytar.sh - пример скрипта
#!/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 755 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.
OK, все работает, но это не очень функционально. Перед тем как сделать его более полезным, давайте посмотрим на "if". Здесь у нас логическое выражение. В Bash, "=" это оператор для проверки равенства строк. В Bash, всех логические выражения, заключенные в квадратные скобки. Но как понять,что это логическое выражение? Давайте взглянем на левую сторону. Согласно тому, что мы узнали о делении строки, "$(1 ##*.}" удаляет самую длинную построку, соответствующую "*." от начала строки, содержащейся в переменной окружения "1", возвращая результат. Это оставит все после последнего ".". Очевидно, что если файл заканчивается на ".tar", то мы получим "tar", как результат, и условие будет истинным.
Вы можете быть удивлены, что "1" переменная. Очень просто - $1 является первым аргументом командной строки скрипта, $2, второй и т.д. Хорошо, теперь, когда мы рассмотрели функцию, мы можем взять "if" себе на вооружение.
if конструкции
Как и в большинстве языков, Bash имеет свою собственную форму условий "if". При использовании их придерживайтесь формата выше, то есть сохраняйте "if" и "then" на отдельных строках, и ставьте "else" и заканчивающий "fi" выравнивая с ними. Это делает код более удобным для чтения и отладки. В дополнение к "if, else", есть несколько другая форма "if" заявления:
if [ condition ]
then
action
fi
Это выполнит одно действие, только если condition истинно, иначе оно не выполняет никаких действий и продолжит выполнять следующие строки после "fi".
if [ condition ]
then
action
elif [ condition2 ]
then
action2
.
.
.
elif [ condition3 ]
then
else
actionx
fi
Здесь "elif" будет последовательно проверять каждое состояние и выполнит соответствующее действие с первого совпадения (ИСТИНА). Если не подходит ни одно из условий, будет выполняться "else" действие, если оно есть, а затем продолжит выполнение следующих строк всего "if, elif, else" выражения.
В следующий раз
Теперь, когда мы рассмотрели самые основные функции Bash, настало время, чтобы наращивать темпы и написать несколько реальных сценариев. В следующей статье я опишу конструкций циклов, функций, имен, и рассмотрим другие важные темы. Тогда мы будем готовы написать несколько более сложных сценариев. В третьей статье мы сосредоточимся исключительно на очень сложных скриптах и функциях, а также несколько вариантов сценариев Bash с опциями. Увидимся!
Комментариев нет:
Отправить комментарий