Искусство программирования на языке сценариев командной оболочки
Автор: Мендель Купер (Mendel Cooper thegrendel@theriver.com)
Перевод: Андрей Киселев <kis_an@mail.ru>
Посвящения
Посвящается Аните -- источнику очарования
Часть 1. Введение
Shell -- это командная оболочка. Но это не просто промежуточное звено между пользователем и операционой системой, это еще и мощный язык программирования. Программы на языке shell называют сценариями, или скриптами. Фактически, из скриптов доступен полный набор команд, утилит и программ UNIX. Если этого недостаточно, то к вашим услугам внутренние команды shell -- условные операторы, операторы циклов и пр., которые увеличивают мощь и гибкость сценариев. Shell-скрипты исключительно хороши при программировании задач администрирования системы и др., которые не требуют для своего создания полновесных языков программирования.
Глава 1. Зачем необходимо знание языка Shell?
Знание языка командной оболочки является залогом успешного решения задач администрирования системы. Даже если вы не предполагаете заниматься написанием своих сценариев. Во время загрузки Linux выполняется целый ряд сценариев из /etc/rc.d, которые настраивают конфигурацию операционной системы и запускают различные сервисы, поэтому очень важно четко понимать эти скрипты и иметь достаточно знаний, чтобы вносить в них какие либо изменения.
Язык сценариев легок в изучении, в нем не так много специфических операторов и конструкций. [ 1 ]Синтаксис языка достаточно прост и прямолинеен, он очень напоминает команды, которые приходится вводить в командной строке. Короткие скрипты практически не нуждаются в отладке, и даже отладка больших скриптов отнимает весьма незначительное время.
Shell-скрипты очень хорошо подходят для быстрого создания прототипов сложных приложений, даже не смотря на ограниченный набор языковых конструкций и определенную \"медлительность\". Такая метода позволяет детально проработать структуру будущего приложения, обнаружить возможные \"ловушки\" и лишь затем приступить к кодированию на C, C++, Java, или Perl.
Скрипты возвращают нас к классической философии UNIX -- \"разделяй и влавствуй\" т.е. разделение сложного проекта на ряд простых подзадач. Многие считают такой подход наилучшим или, по меньшей мере, наиболее эстетичным способом решения возникающих проблем, нежели использование нового поколения языков -- \"все-в-одном\", таких как Perl.
Для каких задач неприменимы скрипты
для ресурсоемких задач, особенно когда важна скорость исполнения (поиск, сортировка и т.п.)
для задач, связанных с выполнением математических вычислений, особенно это касается вычислений с плавающей запятой, вычислений с повышенной точностью, комплексных чисел (для таких задач лучше использовать C++ или FORTRAN)
для кросс-платформенного программирования (для этого лучше подходит язык C)
для сложных приложений, когда структурирование является жизненной необходимостью (контроль за типами переменных, прототипами функций и т.п.)
для целевых задач, от которых может зависеть успех предприятия.
когда во главу угла поставлена безопасность системы, когда необходимо обеспечить целостность системы и защитить ее от вторжения, взлома и вандализма.
для проектов, содержащих компоненты, очень тесно взаимодействующие между собой.
для задач, выполняющих огромный объем работ с файлами
для задач, работающих с многомерными массивами
когда необходимо работать со структурами данных, такими как связанные списки или деревья
когда необходимо предоставить графический интерфейс с пользователем (GUI)
когда необходим прямой доступ к аппаратуре компьютера
когда необходимо выполнять обмен через порты ввода-вывода или сокеты
когда необходимо использовать внешние библиотеки
для проприетарных, \"закрытых\" программ (скрипты представляют из себя исходные тексты программ, доступные для всеобщего обозрения)
Если выполняется хотя бы одно из вышеперечисленных условий, то вам лучше обратиться к более мощным скриптовым языкам программирования, например Perl, Tcl, Python, Ruby или к высокоуровневым компилирующим языкам -- C, C++ или Java. Но даже в этом случае, создание прототипа приложения на языке shell может существенно облегчить разработку.
Название BASH -- это аббревиатура от \"Bourne-Again Shell\" и игра слов от, ставшего уже классикой, \"Bourne Shell\" Стефена Бурна (Stephen Bourne). В последние годы BASH достиг такой популярности, что стал стандартной командной оболочкой de facto для многих разновидностей UNIX. Большинство принципов программирования на BASH одинаково хорошо применимы и в других командных оболочках, таких как Korn Shell (ksh), от которой Bash позаимствовал некоторые особенности[ 2 ], и C Shell и его производных. (Примечательно, что C Shell не рекомендуется к использованию из-за отдельных проблем, отмеченных Томом Кристиансеном (Tom Christiansen) в октябре 1993 года на Usenet post
Далее, в тексте документа вы найдете большое количество примеров скриптов, иллюстрирующих возможности shell. Все примеры -- работающие. Они были протестированы, причем некоторые из них могут пригодиться в повседневной работе. Уважаемый читатель можеть \"поиграть\" с рабочим кодом скриптов, сохраняя их в файлы, с именами scriptname.sh.[ 3 ] Не забудьте выдать этим файлам право на исполнение (chmod u+rx scriptname), после чего сценарии можно будет запустить на исполнение и проверить результат их работы. Вам следует помнить, что описание некоторых примеров следует после исходного кода этого примера, поэтому, прежде чем запустить сценарий у себя -- ознакомьтесь с его описанием.
Скрипты были написаны автором книги, если не оговаривается иное.
Глава 2. Для начала о Sha-Bang
В простейшем случае, скрипт -- это ни что иное, как простой список команд системы, записанный в файл. Создание скриптов поможет сохранить ваше время и силы, которые тратятся на ввод последовательности команд всякий раз, когда необходимо их выполнить.
Пример 2-1. cleanup: Сценарий очистки лог-файлов в /var/log
# cleanup
# Для работы сценария требуются права root.
cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo \"Лог-файлы очищены.\"
Здесь нет ничего необычного, это простая последовательность команд, которая может быть набрана в командной строке с консоли или в xterm. Преимущество размещения последовательности команд в скрипте состоит в том, что вам не придется всякий раз набирать эту последовательность вручную. Кроме того, скрипты легко могут быть модифицированы или обобщены для разных применений.
Пример 2-2. cleanup: Расширенная версия предыдущего сценария.
#!/bin/bash
# cleanup, version 2
# Для работы сценария требуются права root.
LOG_DIR=/var/log
ROOT_UID=0 # Только пользователь с $UID 0 имеет привилегии root.
LINES=50 # Количество сохраняемых строк по-умолчанию.
E_XCD=66 # Невозможно сменить каталог?
E_NOTROOT=67 # Признак отсутствия root-привилегий.
if [ \"$UID\" -ne \"$ROOT_UID\" ]
then
echo \"Для работы сценария требуются права root.\"
exit $E_NOTROOT
fi
if [ -n \"$1\" ]
# Проверка наличия аргумента командной строки.
then
lines=$1
else
lines=$LINES # Значение по-умолчанию, если число не задано в командной строке
fi
# Stephane Chazelas предложил следующее,
#+ для проверки корректности аргумента, переданного из командной строки,
#+ правда это достаточно сложно для данного руководства.
#
# E_WRONGARGS=65 # Не числовой аргумент
#
# case \"$1\" in
# \"\" ) lines=50;;
# *[!0-9]*) echo \"Usage: `basename $0` file-to-cleanup\"; exit $E_WRONGARGS;;
# * ) lines=$1;;
# esac
#
#* Конец проверки корректности аргумента
cd $LOG_DIR
if [ `pwd` != \"$LOG_DIR\" ] # или if [ \"$PWD\" != \"$LOG_DIR\" ]
# Не в /var/log?
then
echo \"Невозможно перейти в каталог $LOG_DIR.\"
exit $E_XCD
fi # Проверка каталога перед очисткой лог-файлов.
# более эффективный вариант:
#
# cd /var/log || {
# echo \"Невозможно перейти в требуемый каталог.\" >&2
# exit $E_XCD;
# }
tail -$lines messages > mesg.temp # Сохранить последние строки в лог-файле.
mv mesg.temp messages
# cat /dev/null > messages
#* Необходимость этой команды отпала, поскольку очистка выполняется выше.
cat /dev/null > wtmp # команды \': > wtmp\' и \'> wtmp\' имеют тот же эффект.
echo \"Лог-файлы очищены.\"
exit 0
# Возвращаемое значение 0
#+ указывает на успешное завершение работы сценария.
Если вы не желаете полностью вычищать системные логи, то выше представлена улучшенная версия предыдущего сценария. Здесь сохраняются последние несколько строк (по-умолчанию -- 50).
Если файл сценария начинается с последовательности #!, которая в мире UNIX называется sha-bang, то это указывает системе какой интерпретатор следует использовать для исполнения сценария. Это двухбайтовая последовательность, или[ 4 ] -- специальный маркер, определяющий тип сценария, в данном случае -- сценарий командной оболочки (см. man magic). Более точно, sha-bang определяет интерпретатор, который вызывается для исполнения сценария, это может быть командная оболочка (shell), иной интерпретатор или утилита[ 5 ].
Служебные символы, используемые в текстах сценариев.
команда \"точка\". Эквивалент команды source (см. Пример 11-18). Это встроенная команда bash.
пустая команда. [двоеточие] Это эквивалент операции \"NOP\" (no op, нет операции). Может рассматриваться как синоним встроенной команды true. Команда \":\" так же является встроенной командой Bash, которая всегда возвращает \"true\" (0).
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f
Каждая, из приведенных выше сигнатур, приводит к вызову различных интерпретаторов, будь то /bin/sh -- командный интерпретатор по-умолчанию (bash для Linux-систем), либо иной[ 6 ]. При переносе сценариев с сигнатурой #!/bin/sh на другие UNIX системы, где в качестве командного интерпретатора задан другой shell, вы можете лишиться некоторых особенностей, присущих bash. Поэтому такие сценарии должны быть POSIX совместимыми[ 7 ].
Обратите внимание на то, что сигнатура должна указывать правильный путь к интерпретатору, в противном случае вы получите сообщение об ошибке -- как правило это \"Command not found\".
Сигнатура #! может быть опущена, если вы не используете специфичных команд. Во втором примере (см. выше) использование сигнатуры #! обязательно, поскольку сценарий использует специфичную конструкцию присваивания значения переменной lines=50. Еще раз замечу, что сигнатура #!/bin/sh вызывает командный интерпретатор по-умолчанию -- /bin/bash в Linux-системах.
if [ $# -ne Number_of_expected_args ]
then
echo \"Usage: `basename $0` whatever\"
exit $WRONG_ARGS
fi
2.1. Запуск сценария
Запустить сценарий можно командой sh scriptname[ 8 ] или bash scriptname. (Не рекомендуется запуск сценария командой sh <scriptname>, поскольку это запрещает использование устройства стандартного ввода stdin в скрипте). Более удобный вариант -- сделать файл скрипта исполняемым, командой chmod.
Это:
chmod 555 scriptname (выдача прав на чтение/исполнение любому пользователю в системе)[ 9 ]
или
chmod +rx scriptname (выдача прав на чтение/исполнение любому пользователю в системе)
chmod u+rx scriptname (выдача прав на чтение/исполнение только \"владельцу\" скрипта)
После того, как вы сделаете файл сценария исполняемым, вы можете запустить его примерно такой командой ./scriptname.[ 10 ] Если, при этом, текст сценария начинается с корректной сигнатуры (\"sha-bang\"), то для его исполнения будет вызван соответствующий интерпретатор.
И наконец, завершив отладку сценария, вы можете поместить его в каталог /usr/local/bin (естественно, что для этого вы должны обладать правами root), чтобы сделать его доступным для себя и других пользователей системы. После этого сценарий можно вызвать, просто напечатав название файла в командной строке и нажав клавишу [ENTER].
2.2. Упражнения
1. Системные администраторы часто создают скрипты для автоматизации своего труда. Подумайте, для выполнения каких задач могут быть написаны сценарии.
2. Напишите сценарий, который выводит дату, время, список зарегистрировавшихся пользователей, и uptime системы и сохраняет эту информацию в системном журнале.
Часть 2. Основы
Глава 3. Служебные символы
#
Комментарии. Строки, начинающиеся с символа # (за исключением комбинации #!) -- являются комментариями.
# Эта строка -- комментарий.
Комментарии могут располагаться и в конце строки с исполняемым кодом.
echo \"Далее следует комментарий.\" # Это комментарий.
Комментариям могут предшествовать пробелы (пробел, табуляция).
# Перед комментарием стоит символ табуляции.
echo \"Символ # не означает начало комментария.\"
echo \'Символ # не означает начало комментария.\'
echo Символ \\# не означает начало комментария.
echo А здесь символ # означает начало комментария.
echo ${PATH#*:} # Подстановка -- не комментарий.
echo $(( 2#101011 )) # База системы счисления -- не комментарий.
# Спасибо, S.C.
Кавычки \" \' и \\ экранируют действие символа #.
В операциях поиска по шаблону символ # так же не воспринимается как начало комментария.
;
Разделитель команд. [Точка-с-запятой] Позволяет записывать две и более команд в одной строке.
echo hello; echo there
Следует отметить, что символ \";\" иногда так же как и # необходимо экранировать.
;;
Ограничитель в операторе выбора case . [Двойная-точка-с-запятой]
case \"$variable\" in
abc) echo \"$variable = abc\" ;;
xyz) echo \"$variable = xyz\" ;;
esac
.
.
\"точка\" может являться частью имени файла . Если имя файла начинается с точки, то это \"скрытый\" файл, т.е. команда ls при обычных условиях его не отображает.
bash$ touch .hidden-file
bash$ ls -l
total 10
-rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook
bash$ ls -al
total 14
drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./
drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook
-rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file
Если подразумевается имя каталога, то одна точка означает текущий каталог и две точки -- каталог уровнем выше, или родительский каталог.
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
Символ точка довольно часто используется для обозначения каталога назначения в операциях копирования/перемещения файлов.
bash$ cp /home/bozo/current_work/junk/* .
Символ \"точка\" в операциях поиска. При выполнении поиска по шаблону , в регулярных выражениях, символ \"точка\" обозначает одиночный символ.
\"
Двойные кавычки . В строке \"STRING\", ограниченной двойными кавычками не выполняется интерпретация большинства служебных символов, которые могут находиться в строке. см. Глава 5.
\'
Одинарные кавычки . [Одинарные кавычки] \'STRING\' экранирует все служебные символы в строке STRING. Это более строгая форма экранирования. Смотрите так же Глава 5.
,
Запятая . Оператор запятая используется для вычисления серии арифметических выражений. Вычисляются все выражения, но возвращается результат последнего выражения.
let \"t2 = ((a = 9, 15 / 3))\" # Присваивает значение переменной \"a\" и вычисляет \"t2\".
\\
escape. [обратный слэш] Комбинация \\X \"экранирует\" символ X. Аналогичный эффект имеет комбинация с \"одинарными кавычками\", т.е. \'X\'. Символ \\ может использоваться для экранирования кавычек \" и \'.
Более детальному рассмотрению темы экранирования посвящена Глава 5.
/
Разделитель, используемый в указании пути к каталогам и файлам. [слэш] Отделяет элементы пути к каталогам и файлам (например /home/bozo/projects/Makefile).
В арифметических операциях -- это оператор деления.
`
Подстановка команд. [обратные кавычки] Обратные кавычки могут использоваться для записи в переменную команды `command`.
:
:
echo $? # 0
Бесконечный цикл:
while :
do
operation-1
operation-2
...
operation-n
done
# То же самое:
# while true
# do
# ...
# done
Символ-заполнитель в условном операторе if/then:
if condition
then : # Никаких действий не производится и управление передается дальше
else
take-some-action
fi