- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF135:Bash
Материал из Linuxformat.
Версия 11:55, 8 октября 2011
- Учебник Bash Bash Извернитесь в Linux, сэкономив время и упростив себе жизнь
Содержание |
Bash: Простые и быстрые скрипты
- Написать полезный скрипт просто. В первой части нашей новой серии о программировании Ник Вейч приглашает вас порезвиться с Bash.
А ведь вы уже можете писать скрипты (сценарии) Bash – разве что они не очень сложны. Рискну предположить, что чаще всего в командной строке вы применяете такой скрипт:
> ls a.png a.txt bash1.sh b.png b.txt c.txt x.jpg y.jpg z.gif
Да, это скрипт Bash. Нет, правда – но только в нем всего одна строка. В нем не используются специальные возможности Bash, просто выполняется простая команда для вывода содержимого текущего каталога. Все еще не верите? Сделаем кое-что еще. Скажем, вы рискнули вывести содержимое каталога в подробном виде:
> ls -l total 4 -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 a.png -rw-rw-r--. 1 evilnick evilnick 2868 Jun 29 12:55 a.txt -rw-rw-r--. 1evilnick evilnick 151 Jun 29 12:49 bash1.sh -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 b.png -rw-rw-r--. 1evilnick evilnick 223 Jun 29 12:55 b.txt -rw-rw-r--. 1 evilnick evilnick 120 Jun 29 12:56 c.txt -rw-rw-r--. 1 evilnick evilnick 3486 Jun 29 12:56 x.jpg -rw-rw-r--. 1 evilnick evilnick 3486 Jun 29 12:56 y.jpg -rw-rw-r--. 1 evilnick evilnick 19273 Jun 29 12:56 z.gif
Ну не круто? Мы воспользовались «ключом», опцией команды ls, который выводит более подробную информацию. Теперь, допустим, нужно вывести список всех файлов изображений. Как это сделать? Попробуем:
> ls -l *.png -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 a.png -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 b.png
Ага! Спецсимвол * – не часть команды ls, а часть оболочки Bash. Bash интерпретирует команду, встречает спецсимвол и заменяет его соответствующими значениями. Итак, мы пишем скрипт Bash! Одна из выгод добавочных знаний о Bash и о работе оболочки в том, что вы можете взглянуть на проблему шире и сделать что-то чуть более умное и полезное:
>ls -l *.{gif,png,jpg} -rw-rw-r--. 1evilnick evilnick 2450 Jun 29 12:55 a.png -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 b.png -rw-rw-r--. 1 evilnick evilnick 3486 Jun 29 12:56 x.jpg -rw-rw-r--. 1 evilnick evilnick 3486 Jun 29 12:56 y.jpg -rw-rw-r--. 1 evilnick evilnick 19273 Jun 29 12:56 z.gif
Что же тут произошло? Это одна из самых простых уловок, которые можно найти в скриптах Bash – раскрытие скобок. В общем случае, Bash раскрывает содержимое скобок как все, что идет перед скобками (или после) плюс каждый отдельный элемент внутри скобок. В данном случае, после раскрытия скобок мы получим команду ls -l *.gif *.jpg *.png.
Команда стала не намного короче, но немного изящнее, и если список возможных расширений файлов будет расти, вы сэкономите время и силы. Применяйте этот прием в будущих скриптах. Теперь, убедившись, что изучение скриптов Bash может весьма пригодиться, приостановимся и рассмотрим, чтоже такое Bash.
{{{Содержание}}}
Если вы беретесь за дело всерьез, найдите себе удобный текстовый редактор. В Emacs, Vi, Gedit и Kate есть подсветка синтаксиса для Bash, и это плюс. Среда оболочки – просто среда: сцена или окружение, в котором обычные команды могут выполнять свои обязанности. Почти каждая команда, которую вы когда-либо выполняли и которая что-то делает, не зависит от оболочки, но без оболочки у этих команд не будет рабочего контекста. Скрытая за внешним лоском шикарного Gnome/KDE/другого графического интерфейса оболочка жизненно необходима для работы компьютера – он и запуститься не смог бы без среды, где выполняются команды. Строка за строкой, скромные скрипты Bash при загрузке системы настраивают сеть, монтируют устройства, запускают сервисы, открывают оконный менеджер; и множество скриптов работает в фоновом режиме, выполняя скрытые задачи поддержки ОС в рабочем состоянии.
Сделаем больше
Многие скажут, что Bash хорош для простых задач, но не годится, когда дело доходит до серьезных вещей, вроде взаимодействия по сети. Хотя в таких случаях подход Bash и в самом деле немного труднее и нуднее (а также и менее познаваем), это отнюдь не значит, что пользоваться им нельзя, и мы рассмотрим несколько подобных примеров в будущем. А что касается причины пользоваться Bash, когда то же самое легко сделать на Python или PHP... ну, иногда Bash – это все, что у вас есть. Мы уже отметили, что практически во всех версиях Linux, встраиваемых и прочих, есть Bash или Bash-подобная среда; поэтому не худо и знать, на что способны даже простейшие утилиты.
Первое, что следует рассмотреть – как мы поступили бы в случае языка программирования – это использование переменных. О разных типах данных забудьте, поскольку тип в основном один: строки. Но строки очень гибкие. Взгляните на короткий пример:
> foo=”ploppy” > echo $foo ploppy > echo $foo+1 ploppy+1 > foo=”ls” > echo $foo ls > foo=”$foo -al” > echo $foo ls -al > echo ‘$foo’ $foo > echo “$foo” ls -al > $foo *.png -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 a.png -rw-rw-r--. 1 evilnick evilnick 2450 Jun 29 12:55 b.png
Прежде всего отметим, что переменная определяется путем ввода ее имени, знака равенства и значения. Не окружайте знак равенства пробелами, иначе Bash выдаст ошибку. Имена переменных должны начинаться с буквы или символа подчеркивания и могут содержать цифры, буквы и символы подчеркивания.
Есть соглашение задавать все имена переменных в верхнем регистре. Однако присутствует множество встроенных переменных и переменных окружения, и задавая имена собственных переменных в нижнем регистре, часто можно избежать конфликта имен.
Так как переменная является строкой, установка значения «нечто+1» просто допишет к строке ‘+1’. Об арифметических операциях мы вкратце поговорим потом.
Знак $ перед именем переменной внутри двойных кавычек приведет к тому, что ее значение будет раскрыто. А внутри одиночных кавычек Bash не делает никаких подстановок, и вы получите просто литерал.
Для раскрытия переменной кавычки также можно вообще опустить. Это довольно удобно, так как означает, что можно сохранять и выполнять команды из переменных, в чем мы убеждаемся в последнем примере.
Нечто полезное
Прежде чем погрязать в деталях, рассмотрим одну штуку, способную пригодиться при написании скрипта. Вот полный скрипт, который можно запустить на домашнем компьютере. Всякие там переменные делают в нем свои делишки. Наберите этот код в текстовом редакторе, сохраните под именем example1.sh и запустите из оболочки командой bash example1.sh:
#!/bin/bash ROOT_UID=0 ERROR_PERMISSION=77 if [ “$UID” != “$ROOT_UID” ] then echo “You must run this script as root” exit $ERROR_PERMISSION fi exit 0
Первая строка начинается с комбинации символов, часто называемой «шалаш» [shebang] – #!. Это особый вид строки комментария, которая сообщает вызвавшей ее оболочке, что если строку выполнять как команду, ее нужно передать исполняемому файлу, путь к которому указан далее. Короче говоря, это просто способ выполнить скрипт. Перейдем к более интересному…
Помните, мы говорили, что из Bash можно обращаться к переменным окружения? UID – одна из этих переменных; она содержит идентификатор пользователя (в виде числа), запустившего оболочку Bash. Это число равно 0 для суперпользователя-root, а для обыкновенных пользователей зависит от используемого дистрибутива; обычно для пер-вого пользователя берется 501.
Стоит ввести в привычку заканчивать скрипт командой exit 0, если все действия завершились успешно. При связке скриптов друг с другом условия выхода важны.
Выражение if – стандартный оператор условия. Анализируемое условие помещается в квадратных скобках. Здесь мы используем оператор сравнения !=, который означает «не равно» (и его также можно записать как “ -ne “). Отсюда ясно, что если идентификатор пользователя равен значению переменной для root, условие внутри квадратных скобок ложно и скрипт переходит к выражению fi, после сравнения. Если значения не равны, выполнение переходит к командам после then, мы получаем сообщение и скрипт завершается.
Не считая ряда зарезервированных значений (0–2, 126–165 и 255), жестких правил насчет кодов выхода нет. По соглашению, для ошибок, связанных с отказом в доступе, часто используется 77, но можно употреблять и свои коды выхода; главное – помнить, что они означают.
Итак, наш скрипт ничего не делает, но может стать началом другого, более плодотворного. Он достаточно безопасен – обычно из Bash нельзя изменить значение UID, но более полезен как проверка перед выполнением неких действий, требующих прав доступа root. Bash поддерживает конструкции else и elif; также существуют особые операторы сравнения. Следующий маленький скрипт проверяет, существует ли заданный каталог, и выводит соответствующее сообщение:
#!/bin/bash CHECKDIR=”examples” if [ -d $CHECKDIR ] then echo “examples directory exists” else echo “examples directory does not exist!” fi exit 0
Параметр -d внутри квадратных скобок – специальный оператор, проверяющий, существует ли каталог в файловой системе. Следующий аргумент – имя каталога; в данном случае мы передали его через переменную. Другие операторы сравнения см. во врезке справа вверху на соседней странице.
Зацикливаемся
Мы уже познакомились со стандартным оператором сравнения. Другая конструкция, часто применяемая в программировании – цикл. В Bash существуют различные типы циклов, но чаще всего используется for... do... done. В Bash его можно сделать очень похожим на цикл в C или в других языках, которыми вы, возможно, пользовались:
for (( i = 0 ; i < 10 ; i++ )) do echo $i done
Впрочем, как и в Python, итерация в Bash обычно осуществляется по списку значений – в нашем примере его генерируют внутренние скобки. Чаще всего вы увидите циклы с итерациями по массиву или по списку, сгенерированному из других значений. Например:
for i in $(ls) do echo $i done
Bash, как всегда, вычисляет содержимое скобок. В данном случае это команда, и Bash ее выполняет и создает список результатов, по которому затем будет производиться итерация. Позже мы подробнее поговорим о массивах (которые в Bash по сути одномерные и больше похожи на объект списка в Python), но чаще всего они используются таким образом:
> array=(becky tabitha sue) > echo $array becky > echo $array[1] becky[1] > echo ${array[1]} tabitha > echo ${array[@]} becky tabitha sue > for i in ${array[@]} > do > echo $i > done becky tabitha sue
Просекли фишку? Посмотрите снова: квадратные скобки используются для ссылки на массив по индексам, но Bash не обязан знать, что приставленное в конце чего-то [1] означает индекс. В порядке обхода, нужно после $ окружить все, что относится к переменной, в фигурные скобки, гарантируя обработку этого как единой сущности. Если попробовать получить доступ к мас сиву без индекса, вы просто получите первый элемент (а индексы начинаются с 0). Индекс [@] — особое значение, которое расширяется до всех значений, хранимых в массиве, и мы используем его в последнем цикле for для генерации списка.
И в итоге
Вооружившись изученными приемами, напишем нечто полезное: короткий скрипт. Будучи запущен в каталоге, он найдет все файлы изображений определенного типа и переместит их в собственный подкаталог images:
#!/bin/bash filetypes=( jpg png gif ) IMGDIR=”images” if [ -d $IMGDIR ] then echo “directory already exists, continuing...” else mkdir $IMGDIR fi for type in ${filetypes[@]} do for i in $( ls *.$type ) do echo “this file $i is being moved” mv $i $IMGDIR done done exit 0
Это можно сделать и компактнее, но по коду, структурированному таким образом, проще понять, что происходит. Мы создали массив для хранения типов файлов, которые нужно отобрать; затем воспользовались оператором if…, чтобы понять, существует ли каталог назначения, или создать его в противном случае.
Первый цикл пробегает по элементам массива. В начале второго цикла выполняется команда ls для получения списка соответствующих файлов, затем с помощью команды mv файлы переносятся в каталог назначения.
Сохранив файл, выполните команду
chmod ugo+x sortimage.sh
Она установит бит исполнения для скрипта, и его можно будет запускать напрямую, без команды bash.
В текущей версии скрипта эта команда будет получать рабочий каталог из любого места в файловой системе, откуда вы ее запускаете. Файл можно было бы сохранить в /usr/sbin или где угодно еще в пути команды и вызывать ее откуда вам заблагорассудится.
Итак, мы обнаружили, что вы знаете о Bash гораздо больше, чем вам казалось, узнали, как пользоваться переменными, и познакомились с основными конструкциями скрипта. На следующем уроке мы поговорим о включении в скрипт внешних команд и их запуске через Bash, и добавим интерактивности вашим программам, написав собственный скрипт резервного копирования. До встречи!
Другие полезные сравнения
Для других вариантов условий помещайте в скобки оператора сравнения такие конструкции:
[ -a $FIlE ] | Истинно, если $FILE существует. |
---|---|
[ -d $FIlE ] | Истинно, если $FILE существует и это каталог. |
[ -f $FIlE ] | Истинно, если $FILE существует и это обыкновенный файл. |
[ -h $FIlE ] | Истинно, если $FILE существует и это символическая ссылка. |
[ -r $FIlE ] | Истинно, если $FILE существует и его можно прочитать. |
[ -s $FIlE ] | Истинно, если $FILE существует и его размер больше нуля. |
[ -w $FIlE ] | Истинно, если $FILE существует и в него можно писать. |
[ -x $FIlE ] | Истинно, если $FILE существует и он исполняемый. |
[ -O $FIlE ] | Истинно, если $FILE существует и его владельцем является пользователь с действующим идентификатором пользователя. |
[ -N $FIlE ] | Истинно, если $FILE существует и изменился с момента последнего чтения. |
[ $FIlE1 -nt $FIlE2 ] | Истинно, если $FILE1 изменился позже $FILE2 или если $FILE1 существует, а $FILE2 нет. |
[ $FIlE1 -ot $FIlE2 ] | Истинно, если $FILE1 старше, чем $FILE2, или если $FILE2 существует, а $FILE1 – нет. |
[ $FIlE1 -ef $FIlE2 ] | Истинно, если $FILE1 и $FILE2 ссылаются на один и тот же файл. |
Хитрости с клавишами
В Bash предусмотрено множество комбинаций клавиш, и они более или менее соответствуют комбинациям клавиш Emacs, так что если вы с ними знакомы, это преимущество. Вот несколько самых ценных – по крайней мере, попробуйте запомнить те, что покажутся вам полезными:
HOME | Перемещает курсор в начало строки. |
---|---|
END | Перемещает курсор в конец строки. |
Стрелка вверх | Восстанавливает предыдущую команду. |
Стрелка вниз | Восстанавливает следующую команду. |
Ctrl+R | Ищет последнюю команду в истории, содержащую указанные символы. |
Ctrl+O | Выполняет команду, найденную с помощью Ctrl+R. |
Ctrl+L | Очищает экран. |
Ctrl+U | Очищает содержимое строки перед курсором. |
Ctrl+K | Очищает содержимое строки после курсора. |
Ctrl+C | Отправляет текущей задаче сигнал SIGINT, который обычно завершает ее. |
Ctrl+Z | Отправляет текущей задаче сигнал SIGTSTP, который приостановит ее. (Затем ее можно возобновить командой fg). Чтобы вернуться к ней позже, можно набрать fg [‘имя процесса или идентификатор задания’]. |
Alt+F | Перемещает курсор на одно слово вперед в строке. |
Alt+B | Перемещает курсор на одно слово назад в строке. |
Alt+Del | Удаляет слово перед курсором. |
Alt+D | Удаляет слово после курсора. |