Алексей
Снастин - независимый разработчик ПО, консультант и переводчик с
английского языка технической и учебной литературы по ИТ. Принимал
участие в разработке сетевых офисных приложений типа клиент/сервер на
языке С в среде Linux.
Описание: Командная строка, командная оболочка,
командный скрипт или сценарий – эти фразы вызывают неоднозначную
реакцию у тех, кто в той или иной степени использует компьютеры.
Опытные пользователи Unix (к которым я причисляю и себя) понимающе
улыбаются и одобрительно кивают головой. Те, кто застал MS-DOS,
недовольно морщатся. Поколение Windows смотрит с недоумением и пожимает
плечами. Впрочем, опытные администраторы Windows понимают, о чём идёт
речь, но особого энтузиазма не выказывают.
Нынешние пользователи Linux вроде бы знают о преимуществах
командной строки, но далеко не все (и не всегда) их используют. Причины
разные. С одной стороны – стремительный прогресс качества и удобства
графического интерфейса пользователя в Linux-средах, с другой –
сложность и относительная трудоёмкость работы в командной строке. Я
вовсе не иронизирую, используя слова сложность и трудоёмкость, –
набирать на клавиатуре длинные команды действительно не так-то просто.
Но если уделить немного времени и внимания освоению некоторых приёмов
работы и основ программирования в командной оболочке, то результаты вас
не разочаруют.
Дата: 22.12.2009
Уровень сложности: средний
Комментарии:
Командная
оболочка в любых unix-подобных системах, к которым относится и
GNU/Linux, является обычной программой, запускаемой как в текстовой
консоли (которая используется всё реже), так и в графической среде – в
окне эмулятора терминала, доступного в любой Linux-системе.
Ее задача проста и очевидна: принять строку (или строки) ввода,
произвести их синтаксический анализ и на основе результатов этого
анализа отреагировать соответствующим образом – выполнить команду,
запустить программу, вывести диагностическое сообщение и т.п.
Почти во всех дистрибутивах Linux для пользователей по умолчанию
назначается командная оболочка bash (Bourne Again SHell – ещё одна
командная оболочка Бурна; Стив Бурн [Steve Bourne] – автор первой
командной оболочки в Unix – sh). Фактически она стала неофициальным
стандартом, и усовершенствование ее функциональных возможностей
продолжается непрерывно. Существуют и другие командные оболочки – tcsh
(версия C-shell), ksh (Korn Shell), zsh и т.д. – у каждой есть свои
достоинства и недостатки, а также свои группы поклонников. Тем не
менее, bash более привычна широким массам пользователей с различными
уровнями подготовки, потому я и остановил свой выбор на ней. Стоит
также отметить, что какими бы возможностями ни обладали различные
оболочки, все они совместимы со своим идеологическим прародителем –
Bourn Shell (sh). Иными словами, скрипт, написанный для sh, будет
корректно работать в любой современной оболочке (обратно, вообще
говоря, неверно).
Может возникнуть вопрос: зачем возиться с командной строкой, если
существуют удобные и красивые графические интерфейсы? Тому есть
множество причин. Во-первых, далеко не все операции удобнее и быстрее
выполнять с помощью графического интерфейса. Во-вторых, каждая
программа следует основополагающему принципу Unix-систем: делать чётко
определённую работу и делать её хорошо. Иными словами, вы всегда
понимаете, что происходит при запуске той или иной утилиты (если что-то
не вполне понятно, то следует обратиться к man-руководству). В-третьих,
осваивая команды, пробуя их сочетания и комбинации их параметров,
пользователь изучает систему, приобретая ценный практический опыт. Вы
получаете доступ к таким эффективным инструментам, как конвейеры,
позволяющие организовать цепочку команд для обработки данных, средства
перенаправления ввода/вывода, а кроме того, можете программировать
непосредственно в командной оболочке. Пожалуй, на программировании
стоит остановиться подробнее, тем более что многие системные сценарии в
Linux (например, скрипты запуска системных сервисов) написаны для shell.
Итак, командную оболочку можно рассматривать как язык
программирования и как программную среду выполнения одновременно.
Разумеется, этот язык не компилируемый, а интерпретируемый. Он
допускает использование переменных: системных или собственных.
Последовательность выполнения команд программы изменяется с помощью
конструкций проверки условия и выбора соответствующего варианта:
if-then-else и case. Циклы while, until и for позволяют
автоматизировать многократно повторяющиеся действия. Имеется
возможность объединять группы команд в логические блоки. Вы можете даже
писать настоящие функции с передачей в них параметров. Таким образом,
налицо все признаки и характеристики полноценного языка
программирования. Попробуем извлечь из этого двойную пользу – наряду с
изучением основ программирования автоматизируем свою повседневную
работу.
О необходимости регулярного резервного копирования данных знают все,
но у пользователей вечно не хватает времени на эту скучную операцию.
Выход прост – организовать автоматическое создание резервных копий. Это
и будет нашим первым заданием по программированию в командной оболочке.
#!/bin/bash # # Резервное копирование каталогов и файлов из домашнего каталога # Этот командный скрипт можно автоматически запускать при помощи cron #
cd $HOME if [ ! -d archives ] then mkdir archives fi cur_date=`date +%Y%m%d%H%M` if [ $# -eq 0 ] ; then tar czf archive${cur_date}.tar.gz projects bin else tar czf archive${cur_date}.tar.gz $* fi if [ $? = 0 ] ; then mv archive${cur_date}.tar.gz $HOME/archives echo "$cur_date – Резервное копирование успешно завершено." else echo "$cur_date – ОШИБКА во время резервного копирования." fi
Любой командный сценарий (script – скрипт, так называются программы
командной оболочки) начинается со строки идентификатора, в которой явно
задаётся интерпретатор команд с указанием полного пути к нему. Полный
путь – последовательное перечисление всех каталогов, начиная с
корневого, в которые надо войти, чтобы добраться до целевого файла, и,
разумеется, имя этого файла. Запись полного пути чрезвычайно важна для
однозначной идентификации каждого файла в иерархии файловой системы.
Далее следуют четыре строки комментариев. Как только командная
оболочка встречает символ '#', она считает все последующие символы
комментариями и полностью игнорирует их до конца текущей строки.
Поэтому комментарий можно начать не с самого начала строки, а
сопроводить им какую-либо команду.
После комментариев располагается пустая строка. Для командной
оболочки она ничего не значит, и никаких действий не производится. В
сценариях пустые строки обычно вставляют для того, чтобы обеспечить
удобство чтения программного кода.
Наконец-то мы добрались до первой «настоящей» команды. Она позволяет
сменить каталог (Change Directory), т.е. перейти из текущего каталога в
другой, переданный команде как аргумент. В большинстве случаев целевой
каталог задаётся в явной форме, например, cd /tmp или cd projects, но в
нашем случае используется предопределённая системная переменная HOME –
в ней содержится полный путь к домашнему каталогу текущего
пользователя, от имени которого выполняется командный сценарий. Тем
самым мы избавляемся от необходимости вносить изменения в код всякий
раз при смене пользователя, потому что команда возвращает любого в его
личный каталог. Знак доллара '$' перед именем переменной означает, что
необходимо извлечь значение, содержащееся в этой переменной, и
подставить его в командную строку вместо её имени. Особо следует
отметить, что в командном языке оболочки регистров букв имеют важное
значение, т.е. HOME, Home и home – это три различные переменные. По
соглашению, буквами верхнего регистра обозначаются имена системных
переменных: HOME, PATH, EDITOR и т.д. Это соглашение не запрещает
пользователям создавать свои переменные с именами из заглавных букв, но
зачем усложнять себе жизнь, нарушая общепринятые нормы и правила? Не
рекомендуется также изменять значения системных переменных без крайней
необходимости. В общем, соблюдаем простое правило: системные переменные
используем только для чтения, а если потребовалась собственная, то её
имя записываем буквами нижнего регистра.
Нашу первую команду можно было бы записать более кратко:
cd ~
Здесь символ '~' также означает домашний каталог текущего пользователя. Ветераны командной строки выражаются ещё лаконичнее:
cd
Смысл в том, что когда для команды cd не задан никакой аргумент, она выполняет переход в домашний каталог.
На очереди классическая программная конструкция проверки условия и принятия соответствующего решения. Общая схема такова:
if <условие> then <одна или несколько команд> fi
Последнее слово конструкции (if в обратном порядке) выполняет роль
закрывающей скобки, т.е. границы списка команд, выполняемых при
истинности условия. Присутствие fi обязательно, даже если в списке лишь
одна команда.
Для проверки условия, как правило, применяется команда test или её
альтернативная форма записи в квадратных скобках. Иначе говоря, записи
if [ ! -d archives ]
и
if test ! -d archives
абсолютно равнозначны. Я предпочитаю квадратные скобки, поскольку
они более наглядно определяют границы проверяемого условия. И правая, и
левая скобка должны быть обязательно отделены от условия пробелами.
Критерии проверки условия определяются разнообразными флагами.
Команда test распознаёт очень большой их список. В нашем примере
использован флаг -d, позволяющий проверить, соответствует ли заданное
после флага имя реально существующему каталогу (directory). Наиболее
часто при работе с файлами применяются следующие флаги:
-f – существует ли обычный файл с заданным именем;
-r – установлено ли для заданного файла право на чтение из него;
-w – установлено ли для заданного файла право на запись в него;
-x – установлено ли для заданного файла право на его выполнение;
-s – имеет ли заданный файл ненулевой размер.
В нашем случае перед условием стоит восклицательный знак,
обозначающий операцию логического отрицания, поэтому смысл проверяемого
условия становится абсолютно противоположным. Попробуем записать смысл
этих команд на обычном русском языке:
if [ ! -d archives ] Если не существует каталог archives (в текущем каталоге), then то начать выполнение блока команд: mkdir archives создать каталог archives (в текущем каталоге) fi завершить выполнение блока команд.
Как видите, всё оказалось не таким уж и сложным. Немного практики, и
вы без труда сможете читать и самостоятельно создавать подобные
конструкции. Команда создания каталога настолько очевидна, что
дополнительных разъяснений не требуется.
В следующей строке мы создаём собственную локальную переменную
cur_date. В подавляющем большинстве случаев переменные создаются
простым присваиванием конкретного значения, например:
ten=10 string="Это строка текста"
Но в нашем примере применяется небольшая хитрость. Обратите
внимание, что после знака равенства – символа присваивания – записана
команда в обратных кавычках. Такая форма записи позволяет присвоить
переменной не саму строку, а результат её выполнения. Здесь это вывод
команды date, которая возвращает текущую дату и время в формате,
определяемом списком параметров:
%Y – текущий год в полной форме, т.е. из четырёх цифр (например, 2009);
%m – номер текущего месяца (например, 09 – для сентября);
%d – номер текущего дня;
%H – текущий час в 24-часовом формате;
%M – текущая минута.
Таким образом, если выполнить команду
cur_date=`date +%Y%m%d%H%M`
десятого сентября 2009 года в 22:45, то переменной cur_date будет
присвоено строковое значение "200909102245". Цель этого ухищрения –
сформировать уникальное, не повторяющееся имя архивного файла. Если вы
намерены запустить несколько экземпляров программы в течение одной
минуты, то можете улучшить уникальность имён, добавляя ещё и текущие
секунды. Как? Изучите руководство утилиты date (man date) – в этом нет
ничего сложного.
Прежде чем приступить к созданию файла архива, необходимо
определить, какие именно каталоги мы будем сохранять в нём. Для большей
гибкости можно задать набор каталогов, архивируемых по умолчанию, но
предусмотреть возможность замены этого набора списком каталогов,
передаваемым как аргумент в наш командный сценарий. Для этого
используются специальные переменные командной оболочки: $# – число
переданных в сценарий параметров и $* – все переданные параметры,
записанные в формате одной строки.
if [ $# -eq 0 ] ; then
Проверка условия «если число переданных параметров равно нулю», то
выполнить следующую команду. Отметим, что ключевое слово then можно
записать в строке условия, отделив его от условного выражения точкой с
запятой.
tar czf archive${cur_date}.tar.gz projects bin
Команда создания архивного файла и сжатия этого файла. Сама утилита
tar не выполняет сжатие, а только лишь собирает все заданные файлы и
каталоги в единый tar-файл. Для этого предназначен первый флаг - c
(create – создать). Сжатие выполняет внешняя программа – здесь это
gzip, вызываемый вторым флагом - z. Если в вашей системе установлена
более эффективная программа сжатия bzip2, то вы можете воспользоваться
ею, изменив команду следующим образом:
tar cjf archive${cur_date}.tar.bz2 projects bin
Третий флаг f сообщает о том, что далее следует имя архивного файла,
поэтому всегда является замыкающим в перечне флагов. Обратите внимание
на то, что при подстановке имя переменной заключено в фигурные скобки.
Это сделано, чтобы явно выделить переменную в окружающей её строке, тем
самым устраняя многие потенциальные проблемы. Расширения архивному
файлу не присваиваются автоматически, вы сами дописываете всё
необходимое. В качестве каталогов, архивируемых по умолчанию, я указал
projects и bin, но вы можете записать здесь имена своих наиболее ценных
каталогов.
Ключевое слово else открывает альтернативную ветвь выполнения.
Команды этого блока начинают работать, если проверка условия даёт
результат «ложь» (в нашем примере: «число переданных параметров
ненулевое», т.е. пользователь задал имена каталогов). В этом случае
команда будет выглядеть так:
tar czf archive${cur_date}.tar.gz $*
Здесь каталоги по умолчанию заменены строкой имён каталогов,
принятой извне. Имеется возможность принимать и обрабатывать каждый
внешний параметр по отдельности, но нам удобнее передать строку
целиком.
В конце программы выполняется ещё одна проверка. В unix-средах все
команды возвращают код статуса завершения своей работы. Если команда
отработала успешно, то она возвращает код 0, в противном случае код
завершения будет ненулевым. Чтобы проверить успешность выполнения
предыдущей команды архивации, воспользуемся ещё одной специальной
переменной $?, в которой всегда содержится значение кода завершения
самой последней команды. Если в переменной $? содержится 0, т.е. файл
резервной копии был успешно создан, то мы перемещаем его в каталог
архивов:
mv archive${cur_date}.tar.gz $HOME/archives
и выдаём соответствующее сообщение:
echo "$cur_date – Резервное копирование успешно завершено."
Если проверка показала, что код завершения операции архивирования не равен нулю, то выводится сообщение об ошибке:
echo "$cur_date – ОШИБКА во время резервного копирования."
На этом работа нашего командного сценария завершается.
Чтобы проверить работу нашей программы, необходимо сохранить
описанный выше исходный код в файле, например, с именем bckp, а затем
для удобства сделать его выполняемым:
chmod 750 bckp
и запустить:
./bckp
для создания резервной копии каталогов, заданных по умолчанию, и
./bckp docs progs works
для создания резервной копии перечисленных каталогов (укажите имена
каталогов, действительно существующих в вашей системе, иначе получите
сообщение об ошибке).
Можно поместить файл bckp в один из каталогов, указанных в системной
переменной PATH. Наиболее предпочтительными местами размещения являются
/usr/local/bin или $HOME/bin, если таковые у вас имеются. После этого
вы можете запускать bckp как системную команду.
Несколько слов об автоматизации резервного копирования. Для этой
цели служит системный планировщик cron, который считывает рабочие
инструкции из специального crontab-файла. Чтобы определить такие
инструкции, необходимо создать и отредактировать свой crontab-файл при
помощи команды:
crontab -e
Инструкции записываются в строго определённом формате (поля разделяются пробелами):
минуты часы день_месяца месяц день_недели команда
Один из вариантов расписания операций резервного копирования может выглядеть следующим образом:
30 23 10,20,30 * * /usr/local/bin/bckp
Это означает, что сценарий резервного копирования (следует указать
полный путь к этому файлу) будет выполняться в 23:30 10-го, 20-го и
30-го числа каждого месяца независимо от дня недели. (Звёздочки
обозначают весь допустимый диапазон значений, в данном случае: каждый
месяц – в 4-м поле, любой день недели – в 5-м поле)
Если вы предпочитаете подводить итоги по неделям, а ваша система
работает круглосуточно, то имеет смысл запланировать резервное
копирование в часы с минимальной нагрузкой:
0 5 * * 3,5 /usr/local/bin/bckp
Здесь резервные копии будут создаваться в 5:00 по средам и пятницам
в каждом месяце (звёздочка в 4-м поле), независимо от числа (звёздочка
в 3-м поле).
Обо всех тонкостях составления расписания можно прочитать в руководстве man 5 crontab.
Рассмотренный в данной статье сценарий резервного копирования
обладает скромными функциональными свойствами. Но не в этом состояла
его главная задача, а в том, чтобы читатель понял, что можно делать в
командной строке, и не только скопировал и выполнил предложенный
командный файл, а заинтересовался расширением его функций, занялся
исследованием необъятных возможностей, предоставляемых командными
оболочками. И если кто-то, прочитав эту статью, попробует
усовершенствовать приведённый здесь код, или напишет собственный
вариант, или реализует свою независимую идею, то я сочту, что основная
цель достигнута.
Алексей
Снастин - независимый разработчик ПО, консультант и переводчик с
английского языка технической и учебной литературы по ИТ. Принимал
участие в разработке сетевых офисных приложений типа клиент/сервер на
языке С в среде Linux.