Аргументы
Когда функция вызывается на выполнение, аргументы функции становятся позиционными параметрами (positional parameters) на время выполнения функции. Они именуются как $n, где n — номер аргумента, к которому мы хотим получить доступ. Нумерация аргументов начинается с 1, так что $1 — это первый аргумент. Мы можем также получить все аргументы сразу с помощью $*, и число аргументов с помощью $#. Позиционный параметр 0 не изменяется.
Если в теле функции встречается встроенная команда return, выполнение функции прерывается и управление передается команде, стоящей после вызова функции. Когда выполнение функции завершается, позиционным параметрам и специальному параметру # возвращаются те значения, которые они имели до начала выполнения функции.
Арифметичекие подстановки (Arithmetic Expansion)
Арифметические подстановки позволяют вычислить значение арифметического выражения и подставить вместо него результат. Существует две формы задания арифметических подстановок:
$[expression]
$((expression)),
где expression трактуется так, как если бы оно было заключено в двойные кавычки, но встречающиеся в expression двойные кавычки трактуются как простой литерал. Внутри expression выполняются подстановки параметров и команд.
Синтаксис выражения expression подобен синтаксису арифметических выражений в языке C, подробнее об этом можно прочитать в разделе ARITHMETIC EVALUATION man-страницы по каманде bash. Например, команда
[user]$ echo $(( 2 + 3 * 5 ))
в качестве результата выдает "17".
Если выражение некорректно, bash выдает сообщение об ошибке.
Что такое оболочка?
Как уже упоминалось выше, хотя мы часто говорим, что "пользователь работает с операционной системой", фактически это не верно, поскольку на деле взаимодействие с пользователем организует специальная программа. Существует два вида таких программ — оболочка, или shell, для работы в текстовом режиме (интерфейс командной строки) и графический интерфейс пользователя GUI (Graphical User Interface), организующий взаимодействие с пользователем в графическом режиме.
Сразу надо сказать, что в принципе любая программа в Linux может быть запущена как через оболочку (если запущен X-сервер, см. гл. 6), так и через графический интерфейс пользователя. Запуск программ из оболочки эквивалентен (двойному) щелчку мышкой по иконке программы в GUI. Передача аргументов программе в текстовом режиме аналогична тому, что мы "бросаем" что-то на иконку программы в графическом. Но, с другой стороны, некоторые программы не приспособлены для запуска через GUI и, соответственно, могут быть исполнены, только из командной строки.
Собственно говоря, название "оболочка" не выдерживает критики. На мой взгляд правильнее было бы называть эту программу командным процессором, как называют command.com в MS DOS, или интерпретатором команд. Но так уж повелось во всех UNIX-системах, что интерпретатор команд для текстового режима называют оболочкой.
Когда-то (в первых UNIX-системах) это была программа с именем sh, которое было сокращением от shell. Потом были разработаны несколько ее улучшенных вариантов, в частности, Bourne shell — расширенная версия sh, написанная Стивом Борном (Steve Bourne). В рамках проекта GNU (проект Р.Столлмана по разработке свободного ПО, см. www.gnu.org) была создана оболочка bash, название которой расшифровывается как Bourne-again shell, т. е. "снова оболочка Борна". По-английски в этом названии просматривается еще и игра слов, связанная с тем, что Bourne звучит как borne (рождаться, рожденный), и получается "заново рожденная shell". Оболочка bash была написана Брайеном Фоксом (Brian Fox — основной разработчик) и Четом Рэми (Chet Ramey). Именно bash мы и будем далее рассматривать, и всюду ниже, где говорится об оболочке вообще, вы смело можете считать, что речь идет о bash.
Сама по себе оболочка bash не выполняет никаких прикладных задач. Но она обеспечивает выполнение всех приложений: нахождение вызываемых программ, их запуск и организацию ввода/вывода. Кроме того, оболочка отвечает за работу с переменными окружения и выполняет некоторые преобразования (подстановки) аргументов. Но главное свойство оболочки, которое делает ее мощным инструментом пользователя — это то, что она включает в себя простой язык программирования. Как давно доказано в математике, любой алгоритм можно построить из пары-тройки основных операций и одного условного оператора. Реализацию условных операторов (а также операторов цикла) и берет на себя оболочка. Она использует все остальные утилиты и программы (и те, которые имеются в составе операционной системы, и те, что устанавливаются отдельно) как базовые операции поддерживаемого ею языка программирования, обеспечивает передачу им аргументов, а также передачу результатов их работы другим программам или пользователю. В результате получается очень мощный язык программирования. И в этом основная сила и одна из существенных функций оболочки.
Прежде чем читать дальше этот раздел, вернитесь ненадолго к и еще раз просмотрите основные комбинации клавиш, используемые для управления вводом в текстовом режиме. Вспомните по крайней мере то, как пользоваться клавишами <Ctrl>+<C>, <Ctrl>+<D>, <Tab> и клавишами со стрелками.
Фильтры
Последний из приведенных выше примеров (с командой grep) можно использовать для иллюстрации еще одного важного понятия, а именно, программы-фильтра. Фильтры — это команды (или программы), которые воспринимают входной поток данных, производят над ним некоторые преобразования и выдают результат на стандартный вывод (откуда его можно перенаправить куда-то еще по желанию пользователя). К числу команд-фильтров относятся уже упоминавшиеся выше команды cat, more, less, wc, cmp, diff, а также следующие команды.
Таблица 5.1. Команды-фильтры
Команда
Особым фильтром является команда tee, которая "раздваивает" входной поток, с одной стороны направляя его на стандартный вывод, а с другой — в файл (имя которого вы должны задать). Легко видеть, что по своему действию команда tee аналогична оператору перенаправления 1>&file.
Возможности фильтров можно существенно расширить за счет использования регулярных выражений, позволяющих организовать, например, поиск по различным, зачастую очень сложным, шаблонам.
О перенаправлении и фильтрах можно было бы говорить очень много. Но этот материал имеется в большинстве книг по UNIX и Linux, например у Петерсена и Келли-Бутла . Поэтому ограничимся сказанным, и перейдем к рассмотрению так называемой среды или окружения, создаваемого оболочкой.
Функция вычисления факториала fact
Еще один пример:
fact()
{
if [ $1 = 0 ]; then
echo 1;
else
{
echo $(( $1 * $( fact $(( $1 — 1 )) ) ))
};
fi
}
Это функция факториала, пример рекурсивной функции. Обратите внимание на арифметическое расширение и подстановку команд.
Команда cat
Мы уже рассматривали кратко команду cat в предыдущем разделе. В данном разделе эта команда интересует нас в основном потому, что чаще всего она работает как раз с входным и выходным потоками. По умолчанию выход команды cat направляется в выходной поток. Чтобы убедиться, что эта команда по умолчанию воспринимает входной поток, запустите команду cat без аргументов. В результате курсор переместится в новую строку, и более как будто ничего не будет происходить. В это время команда ожидает поступления символов во входном потоке. Введите любой символ, и вы увидите, что он сразу же появился на экране, что говорит о том, что программа сразу же направила его в выходной поток. Можно продолжить ввод символов, и они также появятся на экране.
Обычно клавиатура настроена на построчный ввод, поэтому если вы нажмете клавишу <Enter>, последняя набранная строка передается команде cat, которая вновь выводит данные на монитор через стандартный вывод. Таким образом, каждая строка будет показана дважды: один раз при наборе и второй раз — командой cat.
Если нажать комбинацию клавиш <Ctrl>+<D>, которая служит командой окончания процедуры ввода, вы вновь вернетесь к подсказке в командной строке. Можно также использовать комбинацию клавиш <Ctrl>+<C>, которая является в оболочке командой завершения работы запущенной программы.
Если команде cat в качестве аргумента задать имя файла, это будет означать, что содержимое файла будет направлено во входной поток, откуда его примет команда cat и выдаст в выходной поток. Но это только частный случай перенаправления ввода, очень полезного механизма оболочки, который, безусловно, заслуживает более подробного рассмотрения.
Команда echo
Команда echo предназначена для выдачи на стандартный вывод строки символов, которая задана ей в качестве аргумента. После этого она выдает сигнал перевода строки и завершается. Попробуйте выполнить команду
[user]$ echo ‘Привет, дружище!’
и, думаю, дальнейших пояснений не потребуется (только используйте именно одиночные кавычки, иначе результат может быть несколько иным).
Команда export
Когда оболочка запускает на выполнение какую-то программу или команду, она передает им часть переменных окружения. Для того, чтобы переменная окружения передавалась запускаемому из оболочки процессу, ее нужно задавать с помощью специальной команды export, т. е. вместо
[user]$ name=value
надо записать
[user]$ export name=value
В таком случае все запускаемые из оболочки программы (в том числе вторичные экземпляры самой оболочки) будут иметь доступ к заданным таким образом переменным, т. е. могут вызывать их значения по именам.
Команда sh
Вы всегда можете запустить новый экземпляр оболочки bash, дав команду bash или sh. При этом можно заставить новый экземпляр оболочки выполнить какой-то скрипт, если передать имя скрипта в виде аргумента команды bash. Так, для выполнения скрипта myscript надо дать команду "sh myscript".
Если вы заглянете в какой-нибудь файл, задающий скрипт (таких файлов в системе очень много), вы увидите, что первая строка в нем имеет вид: #!/bin/sh. Это означает, что когда мы запускаем скрипт на выполнение как обычную команду, /bin/sh будет выполнять ее для нас. Можно заменить эту строку ссылкой на любую программу, которая будет читать файл и исполнять соответствующие команды. Например, скрипты на языке Perl начинаются со строки вида #!/bin/perl.
Отметим также, что символ # служит для выделения в скриптах комментариев. Все, что стоит в текущей строке после этого символа и до символа конца строки, оболочка будет считать комментариями и игнорировать (т. е. оболочка не рассматривает этот текст как команды). Если хотите убедиться в действии этого символа, введите в командной строке любую команду, поставив перед ней символ #, например, "# ls", и вы увидите, что команда игнорируется оболочкой.
На этом мы завершим сокращенное описание оболочки bash. Конечно, за рамками этого описания остались многие важные вопросы, например, управление процессами, описания встроенных команд, история команд, описание библиотеки readline, сигналы и т. д.. Часть этих вопросов будет отражена в последующих разделах, а остальное вы должны искать в других руководствах или на странице man bash. ()
Локальные переменные (local)
Если мы хотим создать локальный параметр, можно использовать ключевое слово local. Синтаксис ее задания точно такой же, как и для обычных параметров, только определению предшествует ключевое слово local: local name=value.
Вот пример задания функции, реализующей упоминавшуюся выше команду seq:
seq()
{
local I=$1;
while [ $2 != $I ]; do
{
echo -n "$I ";
I=$(( $I + 1 ))
};
done;
echo $2
}
Обратите внимание на опцию -n оператора echo, она отменяет переход на новую строку. Хотя это и несущественно для тех целей, которые мы здесь имеем в виду, это может оказаться полезным для использования функции в других целях.
Оператор ;
Хотя чаще всего пользователь задает команды в командной строке по одной, имеется возможность задать в одной строке несколько команд, которые будут выполнены последовательно, одна за другой. Для этого используется специальный символ -оператор ;. Если не поставить этот разделитель команд, то последующая команда может быть воспринята как аргумент предыдущей. Таким образом, если написать в командной строке что-то вроде:
[user]$ command1 ; command2
то оболочка вначале запустит на выполнение команду command1, дождется, пока ее выполнение завершится, после чего запустит command2, дождется ее завершения, после чего снова выведет приглашение командной строки, ожидая следующих действий пользователя.
Оператор & используется для того, чтобы организовать исполнение команд в фоновом режиме. Если поставить значок & после команды, то оболочка вернет управление пользователю сразу после запуска команды, не дожидаясь, пока выполнение команды завершится. Например, если задать в командной строке "command1 & command2 &", то оболочка запустит команду command1, сразу же затем команду command2, и затем немедленно вернет управление пользователю.
Особым вариантом перенаправления вывода является организация программного канала (иногда называет трубопроводом или конвейером). Для этого две или несколько команд, таких, что вывод предыдущей служит вводом для следующей, соединяются (или разделяются, если вам это больше нравится) символом вертикальной черты — "|". При этом стандартный выходной поток команды, расположенной слева от символа |, направляется на стандартный ввод программы, расположенной справа от символа |. Например:
[user]$ cat myfile | grep Linux | wc -l
Эта строка означает, что вывод команды cat, т. е. текст из файла myfile, будет направлен на вход команды grep, которая выделит только строки, содержащие слово "Linux". Вывод команды grep будет, в свою очередь, направлен на вход команды wc -l, которая подсчитает число таких строк.
Программные каналы используются для того, чтобы скомбинировать несколько маленьких программ, каждая из которых выполняет только определенные преобразования над своим входным потоком, для создания обобщенной команды, результатом которой будет какое-то более сложное преобразование.
Надо отметить, что оболочка одновременно вызывает на выполнение все команды, включенные в конвейер, запуская для каждой из команд отдельный экземпляр оболочки, так что как только первая программа начинает что-либо выдавать в свой выходной поток, следующая команда начинает его обрабатывать. Точно так же каждая следующая команда выполняет свою операцию, ожидая данных от предыдущей команды и выдавая свои результаты на вход последующей. Если вы хотите, чтобы какая-то команда полностью завершилась до начала выполнения последующей, вы можете использовать в одной строке как символ конвейера |, так и точку с запятой ;. Перед каждой точкой с запятой оболочка будет останавливаться и ожидать, пока завершится выполнение всех предыдущих команд, включенных в конвейер.
Статус выхода (логическое значение, возвращаемое после завершения работы программы) из канала совпадает со статусом выхода, возвращаемым последней командой конвейера. Перед первой командой конвейера можно поставить символ "!", тогда статус выхода из конвейера будет логическим отрицанием статуса выхода из последней команды. Оболочка ожидает завершения всех команд конвейера, прежде чем установить возвращаемое значение.
Оператор case
Формат оператора case таков:
case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
Команда case вначале производит раскрытие слова word, и пытается сопоставить результат с каждым из образцов pattern поочередно. После нахождения первого совпадения дальнейшие проверки не производятся, выполняется список команд, стоящий после того образца, с которым обнаружено совпадение. Значение, возвращаемое оператором, равно 0, если совпадений с образцами не обнаружено. В противном случае возвращается значение, выдаваемое последней командой из соответствующего списка.
Следующий пример использования оператора case заимствован из системного скрипта /etc/rc.d/rc.sysinit.
case "$UTC" in
yes|true)
CLOCKFLAGS="$CLOCKFLAGS -u";
CLOCKDEF="$CLOCKDEF (utc)";
;;
no|false)
CLOCKFLAGS="$CLOCKFLAGS --localtime";
CLOCKDEF="$CLOCKDEF (localtime)";
;;
esac
Если переменная принимает значение yes или true, то будет выполнена первая пара команд, а если ее значение равно no или false – вторая пара.
Оператор for
Оператор for работает немного не так, как в обычных языках программирования. Вместо того, чтобы организовывать увеличение или уменьшение на единицу значения некоторой переменной при каждом проходе цикла, он при каждом проходе цикла присваивает переменной очередное значение из заданного списка слов. В целом конструкция выглядит примерно так:
for name in words do list done.
Правила построения списков команд (list) такие же, как и в операторе if.
Пример. Следующий скрипт создает файлы foo_1, foo_2 и foo_3:
for a in 1 2 3 ; do
touch foo_$a
done
В общем случае оператор for имеет формат:
for name [ in word; ] do list ; done
Вначале производится раскрытие слова word в соответствии с правилами раскрытия выражений, приведенными выше. Затем переменной name поочередно присваиваются полученные значения, и каждый раз выполняется список команд list. Если "in word" пропущено, то список команд list выполняется один раз для каждого позиционного параметра, который задан.
В Linux имеется программа seq, которая воспринимает в качестве аргументов два числа и выдает последовательность всех чисел, расположенных между заданными. С помощью этой команды можно заставить for в bash работать точно так же, как аналогичный оператор работает в обычных языках программирования. Для этого достаточно записать цикл for следующим образом:
for a in $( seq 1 10 ) ; do
cat file_$a
done
Эта команда выводит на экран содержимое 10-ти файлов: "file_1", ..., "file_10".
Оператор select
Оператор select позволяет организовать интерактивное взаимодействие с пользователем. Он имеет следующий формат:
select name [ in word; ] do list ; done
Вначале из шаблона word формируется список слов, соответствующих шаблону. Этот набор слов выводится в стандартный поток ошибок, причем каждое слово сопровождается порядковым номером. Если шаблон word пропущен, таким же образом выводятся позиционные параметры. После этого выдается стандартное приглашение PS3, и оболочка ожидает ввода строки на стандартном вводе. Если введенная строка содержит число, соответствующее одному из отображенных слов, то переменной name присваивается значение, равное этому слову. Если введена пустая строка, то номера и соответствующие слова выводятся заново. Если введено любое другое значение, переменной name присваивается нулевое значение. Введенная пользователем строка запоминается в переменой REPLY. Список команд list выполняется с выбранным значением переменной name.
Вот небольшой скрипт:
#!/bin/sh
echo "Какую ОС Вы предпочитаете?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
break
done
echo "Вы бы выбрали $var"
Если сохранить этот текст в файле, сделать файл исполняемым и запустить, на экран будет выдан следующий запрос:
Какую ОС Вы предпочитаете?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#?
Нажмите любую из 4 предложенных цифр (1,2,3,4). Если вы, например, введете 1, то увидите собщение:
“Вы бы выбрали Linux”
Оператор test и условные выражения
Условные выражения, используемые в операторе test, строятся на основе проверки файловых атрибутов, сравнения строк и обычных арифметических сравнений. Сложные выражения строятся из следующих унарных или бинарных операций ("элементарных кирпичиков"):
-a file
Верно, если файл с именем file существует.
-b file
Верно, если file существует и является специальным файлом блочного устройства.
-c file
Верно, если file существует и является специальным файлом символьного устройства.
-d file
Верно, если file существует и является каталогом.
-e file
Верно, если файл с именем file существует.
-f file
Верно, если файл с именем file существует и является обычным файлом.
-g file
Верно, если файл с именем file существует и для него установлен бит смены группы.
-h file или -L file
Верно, если файл с именем file существует и является символической ссылкой.
-k file
Верно, если файл с именем file существует и для него установлен "sticky'' bit.
-p file
Верно, если файл с именем file существует и является именованным каналом (FIFO).
-r file
Верно, если файл с именем file существует и для него установлено право на чтение
-s file
Верно, если файл с именем file существует и его размер больше нуля.
-t fd
Верно, если дескриптор файла fd открыт и указывает на терминал.
-u file
Верно, если файл с именем file существует и для него установлен бит смены пользователя.
-w file
Верно, если файл с именем file существует и для него установлено право на запись.
-x file
Верно, если файл с именем file существует и является исполняемым.
-O file
Верно, если файл с именем file существует и его владельцем является пользователь, на которого указывает эффективный идентификатор пользователя.
-G file
Верно, если файл с именем file существует и принадлежит группе, определяемой эффективным идентификатором группы.
-S file
Верно, если файл с именем file существует и является сокетом.
-N file
Верно, если файл с именем file существует и изменялся с тех пор, как был последний раз прочитан.
file1 –nt file2
Верно, если файл file1 имеет более позднее время модификации, чем file2.
file1 –ot file2
Верно, если файл file1 старше, чем file2.
file1 –ef file2
Верно, если файлы file1 и file2 имеют одинаковые номера устройств и индексных дескрипторов (inode).
-o optname
Верно, если задействована опция оболочки optname. Пояснения см. на странице man bash.
-z string
Верно, если длина строки равна нулю.
-n string
Верно, если длина строки не равна нулю.
string1 == string2
Верно, если строки совпадают. Вместо == может использоваться =.
string1 !== string2
Верно, если строки не совпадают.
string1 < string2
Верно, если строка string1 лексикографически предшествует строке string2 (для текущей локали).
string1 > string2
Верно, если строка string1 лексикографически стоит после строки string2 (для текущей локали).
arg1 OP arg2
Здесь OP — это одна из операций арифметического сравнения: -eq (равно), -ne (не равно), -lt (меньше чем), -le (меньше или равно), -gt (больше), -ge (больше или равно). В качестве аргументов могут использоваться положительные или отрицательные целые.
Из этих элементарных условных выражений можно строить сколь угодно сложные с помощью обычных логических операций ОТРИЦАНИЯ, И и ИЛИ:
!(expression)
Булевский оператор отрицания.
expression1 -a expression2
Булевский оператор AND (И). Верен, если верны оба выражения.
expression1 -o expression2
Булевский оператор OR (ИЛИ). Верен, если верно любое из двух выражений.
Такие же условные выражения используются и в операторах while и until, которые мы рассмотрим чуть ниже.
Операторы && и
Операторы && и являются управляющими операторами. Если в командной строке стоит command1 && command2, то command2 выполняется в том, и только в том случае, если статус выхода из команды command1 равен нулю, что говорит об успешном ее завершении. Аналогично, если командная строка имеет вид command1 command2, то команда command2 выполняется тогда, и только тогда, когда статус выхода из команды command1 отличен от нуля.
Сама техника организации запуска команд на выполнение не является предметом нашего рассмотрения. Можно только кратко сказать, что оболочка должна найти код команды, загрузить его в память, передать команде аргументы, заданные в командной строке, а после завершения выполнения соответствующего процесса передать каким-то образом пользователю или другому процессу результаты выполнения данной команды. Эти этапы мы кратко и рассмотрим.
Итак, первый этап — поиск кода команды. Команды бывают встроенные (те, код которых включен в код самой оболочки) и внешние (код которых расположен в отдельном файле на диске). Встроенную команду оболочка всегда найдет, а для поиска внешней команды пользователь, в принципе, должен указать оболочке полный путь до соответствующего файла. Однако для облегчения жизни пользователей оболочка умеет искать внешние команды в каталогах, которые перечислены в специально заданных "путях поиска". Только если она не находит нужных файлов в таких каталогах, она решает, что пользователь ошибся при вводе имени команды. О том, как включить каталог в пути поиска, будет сказано ниже, а сейчас рассмотрим, как оболочка организует передачу данных исполняемой команде и выдачу результатов пользователю.
Операторы >, < и >>
Для обозначения перенаправления используются символы ">", "<" и ">>". Чаще всего используется перенаправление вывода команды в файл. Вот соответствующий пример:
[user]$ ls -l > /home/jim/dir.txt
По этой команде в файле /home/jim/dir.txt будет сохранен перечень файлов и подкаталогов того каталога, который был текущим на момент выполнения команды ls; при этом если указанного файла не существовало, то он будет создан; если он существовал, то будет перезаписан; если же вы хотите, чтобы вывод команды был дописан в конец существующего файла, то надо вместо символа > использовать >>. При этом наличие пробелов до или после символов > или >> несущественно и служит только для удобства пользователя.
Вы можете направить вывод не только в файл, но и на вход другой команды или на устройство (например, принтер). Так, для подсчета числа слов в файле /home/jim/report.txt можно использовать следующую команду:
[user]$ cat /home/jim/report.txt > wc -w
а для вывода файла на печать — команду:
[user]$ cat /home/jim/report.txt > lpr
Как видите, оператор > служит для перенаправления выходного потока. По отношению к входному потоку аналогичную функцию выполняет оператор <. Приведенный выше пример команды для подсчета числа слов в определенном файле можно переписать следующим образом (обратите внимание на отсутствие команды cat):
[user]$ wc -w < /home/jim/report.txt
Этот вариант перенаправления часто используется в различных скриптах, применительно к тем командам, которые обычно воспринимают ввод (или ожидают ввода) с клавиатуры. В скрипте же, автоматизирующем какие-то рутинные операции, можно дать команде необходимую информацию из файла, в который заранее записано то, что нужно ввести для выполнения этой команды.
В силу того, что символы <, > и >> действуют на стандартные потоки, их можно использовать не только тем привычным образом, как это делается обычно, но и несколько по-другому. Так, следующие команды эквивалентны:
[user]$ cat > file
Операторы if и test (или [ ])
Конструкция условного оператора в слегка упрощенном виде выглядит так:
if list1 then list2 else list3 fi
где list1, list2 и list3 — это последовательности команд, разделенные запятыми и оканчивающиеся точкой с запятой или символом новой строки. Кроме того, эти последовательности могут быть заключены в фигурные скобки: {list}.
Оператор if проверяет значение, возвращаемое командами из list1. Если в этом списке несколько команд, то проверяется значение, возвращаемое последней командой списка. Если это значение равно 0, то будут выполняться команды из list2; если это значение не нулевое, будут выполнены команды из list3. Значение, возвращаемой таким составным оператором if, совпадает со значением, выдаваемым последней командой выполняемой последовательности.
Полный формат команды if имеет вид:
if list then list [ elif list then list ] ... [ else list ] fi
(здесь квадратные скобки означают только необязательность присутствия в операторе того, что в них содержится).
В качестве выражения, которое стоит сразу после if или elif, часто используется команда test, которая может обозначаться также квадратными скобками [ ]. Команда test выполняет вычисление некоторого выражения и возвращает значение 0, если выражение истинно, и 1 в противном случае. Выражение передается программе test как аргумент. Вместо того, чтобы писать
test expression,
можно заключить выражение в квадратные скобки:
[ expression ].
Заметьте, что test и [ — это два имени одной и той же программы, а не какое-то магическое преобразование, выполняемое оболочкой bash (только синтаксис [ требует, чтобы была поставлена закрывающая скобка). Заметьте также, что вместо test в конструкции if может быть использована любая программа.
В заключение приведем пример использования оператора if:
if [ -e textmode2.htm ] ; then
ls textmode*
else
pwd
fi
Об операторе test (или […]) надо бы поговорить особо.
Операторы while и until
Оператор while работает подобно if, только выполнение операторов из списка list2 циклически продолжается до тех пор, пока верно условие, и прерывается, если условие не верно. Конструкция выглядит следующим образом:
while list1 do list2 done.
Пример:
while [ -d mydirectory ] ; do
ls -l mydirectory >> logfile
echo -- SEPARATOR -- >> logfile
sleep 60
done
Такая программа будет протоколировать содержание каталога "mydirectory" ежеминутно до тех пор, пока директория существует.
Оператор until аналогичен оператору while:
until list1 do list2 done.
Отличие заключается в том, что результат, возвращаемый при выполнении списка операторов list1, берется с отрицанием: list2 выполняется в том случае, если последняя команда в списке list1 возвращает ненулевой статус выхода.
Параметры и переменные. Окружение оболочки
Понятие параметра в оболочке bash подобно понятию переменной в обычных языках программирования. Именем (или идентификатором) параметра может быть слово, состоящее из алфавитных символов, цифр и знаков подчеркивания (только первый символ этого слова не может быть цифрой), а также число или один из следующих специальных символов: *, @, #, ?, - (дефис), $, !, 0, _ (подчеркивание).
Говорят, что параметр задан или установлен, если ему присвоено значение. Значением может быть и пустая строка. Чтобы вывести значение параметра, используют символ $ перед его именем. Так, команда
[user]$ echo name
выдаст на экран слово name, а команда
[user]$ echo $name
выдаст значение переменной name (если таковое, конечно, задано).
Переменная IFS
Эта переменная задает разделители полей (Internal Field Separator), которые используются при операции разделения слов при преобразованиях командной строки, выполняемых оболочкой перед тем, как запустить командную строку на исполнение. Значение этой переменной по умолчанию — "<Пробел><Tab><Символ_новой_строки>".
Переменная PATH
Еще одна очень важная переменная имеет имя PATH. Она задает перечень путей к каталогам, в которых bash осуществляет поиск файлов (в частности, файлов с командами) в тех случаях, когда полный путь к файлу не задан в командной строке. Отдельные каталоги в этом перечне разделяются двоеточиями. По умолчанию переменная PATH включает каталоги /usr/local/bin, /bin, /usr/bin, /usr/X11R6/bin, т. е. имеет вид:
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:
Для того, чтобы добавить каталог в этот список, нужно выполнить следующую команду:
[root]# PATH=$PATH:new_path.
При осуществлении поиска оболочка просматривает каталоги именно в том порядке, как они перечислены в переменной PATH.
Отметим, что можно включить в этот список и текущий каталог, добавив в переменную PATH точку. Однако этого не рекомендуется делать по соображениям безопасности: злоумышленник может положить в общедоступный каталог команду, имя которой совпадает с одной из часто выполняемых суперпользователем команд, но выполняющую совершенно другие действия (особенно если текущий каталог стоит в начале перечня путей поиска).
Перенаправление ввода/вывода, каналы и фильтры
Хотя обычно, как было сказано, ввод/вывод программы связаны со стандартными потоками, в оболочке существуют специальные средства для перенаправления ввода/вывода.
Подстановка команд
Подстановка команд является очень мощным инструментов bash. Она заключается в замене имени команды на результат ее выполнения. Существует две формы подстановки команд:
$(command) и `command`
Если применяется вторая из этих форм, то обратный слэш внутри кавычек трактуется как литерал, кроме тех случаев, когда за ним следует $, `, или \. Если же используется форма $(command), все символы внутри скобок составляют команду и ни один из них не считается специальным символом.
Если подстановка производится внутри двойных кавычек, то в результатах подстановки не осуществляется разделение слов и раскрытие шаблонов имен файлов и каталогов.
Подстановка параметров и переменных
Символ $ используется для обозначения операций подстановки параметров, подстановки команд и подстановок арифметических выражений. Выражение или имя, следующее за $, может быть заключено в скобки; что не обязательно, но удобно, так как позволяет отделить заменяемое выражение от следующих за ним слов или символов. Таким образом, чтобы в командной строке вызвать значение параметра (в частности, любой переменной), нужно вставить выражение вида ${parameter}.
Скобки необходимы только в том случае, если имя параметра состоит из нескольких цифр, или когда за именем следует символ, который не должен интерпретироваться как часть имени.
Все значения переменных подвергаются подстановке знака тильды, раскрытию параметров и переменных, подстановке команд, подстановкам арифметических выражений, а также удалению специальных символов цитирования (см. ниже). Разделение слов не производится, за исключением случая "$@" (объяснение см. выше в табл. 5.3). Раскрытие шаблонов имен файлов и каталогов не производится.
Потоки ввода-вывода
Когда программа запускается на выполнение, в ее распоряжение предоставляются три потока (или канала): стандартный ввод (standard input или stdin). По этому каналу данные передаются программе; стандартный вывод (standard output или stdout). По этому каналу программа выводит результаты своей работы; стандартный поток сообщений об ошибках (standard error или stderr). По этому каналу программы выдают информацию об ошибках.
Из стандартного входа программа может только читать, а два других потока могут использоваться программой только для записи.
По умолчанию входной поток связан с клавиатурой, а выходной поток и поток сообщений об ошибках направлены на терминал пользователя. Другими словами, вся выходная информация запущенной пользователем команды или программы, а также все сообщения об ошибках, выводятся в окно терминала. Однако, как мы увидим чуть ниже, можно перенаправить выходные сообщения (например, в файл).
Для того, чтобы продемонстрировать, как работает стандартный поток ошибок, выполните команду ls с неверным аргументом, например, задав в качестве аргумента имя несуществующего файла. В таком случае ls выведет сообщение об ошибке в стандартный поток ошибок. Для нас, однако, в данном случае стандартный поток ошибок неотличим от выходного потока, поскольку сообщение об ошибке мы видим в окне терминала.
Работу со стандартными входным и выходным потоками лучше всего проиллюстрировать на примере команд echo и cat.
Приглашения оболочки
Одна из очень важных переменных имеет имя PS1. Эта переменная задает вид приглашения, которое bash выводит, когда ожидает ввода очередной команды пользователем. По умолчанию этой переменной присвоено значение "\s-\v\$ ". Вообще-то в bash существует четыре приглашения, которые используются в разных ситуациях. Переменная PS1 задает вид строки приглашения, которая выдается тогда, когда оболочка ждет ввода команды. Вторичное приглашение, задаваемое переменной PS2, появляется тогда, когда оболочка ожидает от пользователя ввода еще каких-то данных, необходимых для продолжения работы запущенной команды или программы. По умолчанию переменная PS2 имеет значение ">". Вы уже имели возможность видеть это приглашение, когда запускали команду cat для ввода данных с клавиатуры в файл. Другой пример — команда ftp, после запуска которой приглашение тоже принимает такой вид.
Приглашение, задаваемое переменной PS3, используется в команде select. Приглашение, задаваемое переменной PS4, выводится перед каждой командой, в то время, когда bash отслеживает процесс выполнения. Значение по умолчанию — "+".
Если у вас есть такое желание, вы можете изменить вид переменных PS1 и PS2. При этом можно использовать как любые символы, вводимые с клавиатуры, так и некоторое число специальных символов, которые при формировании строки приглашения декодируются в соответствии с табл. 5.3 (приводим только некоторые из них, для примера; полный список см. в man-странице по утилите bash).
Таблица 5.3. Специальные символы для формирования приглашения
Символ
Текущий номер команды (порядковый номер выполняемой команды в рамках текущей сессии) может отличаться от номера данной команды в списке истории команд, поскольку последний включает в себя команды, которые были сохранены в файле истории команд.
После того, как значение переменной, определяющей подсказку, прочитано оболочкой, в нем могут быть произведены подстановки в соответствии с правилами расширения параметров, подстановок в именах команд и арифметических выражениях, а также разбиения слов (word splitting). Об этих правилах будет рассказано чуть ниже, в разд. 5.7.
Например, после выполнения команды (поскольку в строке имеется пробел, кавычки обязательны)
[root]# PS1="[\u@\h \W]\$"
в стандартном приглашении будет выводиться квадратная скобка, имя пользователя, символ @, имя компьютера, пробел, название текущего каталога (без указания пути), закрывающая квадратная скобка и символ $ (если в оболочке работает простой пользователь) или # (если оболочка запущена от имени пользователя root).
Раскрытие шаблонов имен файлов и каталогов (Pathname Expansion)
Подстановки имен путей и файлов (Pathname expansion) используются для того, чтобы с помощью краткого образца или шаблона указать несколько имен файлов (или каталогов), соответствующих данному шаблону. После разделения слов, если не была задана опция -f, bash производит поиск в каждом слове командной строки символов *, ?, and [. Если будет найдено слово с одним или несколькими вхождениями таких символов, то это слово рассматривается как шаблон, который должен быть заменен словами из лексикографически упорядоченного списка имен путей, соответствующих данному шаблону. Если имен, соответствующих шаблону, не найдено, и переменная nullglob не задана, слово не изменяется. Если эта переменная установлена, а путей, соответствующих шаблону не найдено, слово удаляется из командной строки.
Специальные символы шаблонов имеют следующее значение.
Таблица 5.4. Символы шаблонов
Символ
Шаблоны имен файлов очень часто применяются в командных строках, содержащих команду ls. Представьте себе, что вы хотите просмотреть информацию о содержимом каталога, в котором находится огромное количество разных файлов различных форматов, например, файлов с изображениями форматов gif, jpeg, avi и т. д.. Чтобы получить только список файлов формата jpeg, вы можете использовать команду
[user]$ ls *.jpg
Если в каталоге имеется множество файлов, имена которых представлены четырехзначными номерами, то следующей командой можно вывести только список файлов с номерами от 0200 до 0499:
[user]$ ls -l 0[2-4]??.*
Раскрытие скобок
Раскрытие скобок проще всего пояснить на примере. Предположим, что нам нужно создать сразу несколько подкаталогов в каком-то каталоге, или поменять владельца сразу у нескольких файлов. Эти действия можно выполнить с помощью следующих команд:
[user]$ mkdir /usr/local/src/bash/{old,new,dist,bugs}
[root]# chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}
В первом случае в каталоге /usr/local/src/bash/ будут созданы подкаталоги old, new, dist и bugs. Во втором случае владелец будет изменен у файлов
/usr/ucb/ex
/usr/lib/ex?.?*
/usr/ucb/edit
/usr/lib/ex?.?*
/usr/ucb/ex
/usr/lib/how_ex
/usr/ucb/edit
/usr/lib/how_ex
То есть для каждой пары скобок генерируются несколько отдельных строк (их число равно числу слов, стоящих внутри скобок) путем приписывания к каждому слову из скобок (спереди) того, что стоит перед скобкой, и приписывания в конец каждого полученного слова того, что стоит после скобки. Еще один пример: строка a{d,c,b}e при раскрытии скобок превращается в три слова "ade ace abe".
Раскрытие скобок выполняется до выполнения других видов подстановок в командной строке, причем все специальные символы, встречающиеся в командной строке, в том числе внутри скобок, сохраняются неизменными (они будут интерпретированы на следующих этапах анализа строки).
Раскрытие выражений (expansion)
Когда оболочка получает какую-то командную строку на выполнение, она до начала выполнения команды осуществляет "грамматический разбор" полученной командной строки. Одним из этапов такого "разбора" является раскрытие или подстановка выражений (expansion). В bash имеется семь типов подстановки выражений:
раскрытие скобок (brace expansion);
замена знака тильды (tilde expansion);
подстановка параметров и переменных;
подстановка команд;
арифметические подстановки (выполняемые слева направо);
разделение слов (word splitting);
раскрытие шаблонов имен файлов и каталогов (pathname expansion).
Все эти операции выполняются именно в том порядке, как они здесь перечислены. Рассмотрим их последовательно.
Разделение слов (word splitting)
После завершения подстановок параметров, команд и арифметических выражений оболочка снова анализирует командную строку (в том виде, который она приобрела к этому моменту) и осуществляет разделение слов (word splitting).
Эта операция заключается в том, что в командной строке ищутся все вхождения символов-разделителей, определенных в переменой IFS, и в соответствующих местах строки разделяются на отдельные слова. Если значение IFS равно пустой строке, разделение слов не производится.
Если в командной строке не производилось никаких подстановок, то разбиение на слова не производится.
Разновидности параметров
Параметры разделяются на три класса: позиционные параметры, специальные параметры (именами которых как раз и служат перечисленные только что специальные символы) и переменные оболочки.
Имена (идентификаторы) позиционных параметров состоят из одной или более цифр (только не из одиночного нуля). Значениями позиционных параметров являются аргументы, которые были заданы при запуске оболочки (первый аргумент является значением позиционного параметра 1, и т. д.). Изменить значение позиционного параметра можно с помощью встроенной команды set. Значения этих параметров изменяются также на время выполнения оболочкой одной из функций (об этом будет рассказано ниже, в разд. 5.8).
Специальные параметры являются шаблонами, замена (подстановка) которых производится следующим образом.
Таблица 5.2. Специальные параметры.
Параметр
(дефис)
(подчеркивание)
Специальные параметры, перечисленные в приведенной выше таблице, отличаются тем, что на них можно только ссылаться; присваивать им значения нельзя.
Переменная с точки зрения оболочки — это параметр, обозначаемый именем. Значения переменным присваиваются с помощью оператора следующего вида
[user]$ name=value
где name — имя переменной, а value — присваиваемое ей значение (может быть пустой строкой). Имя переменой может состоять только из цифр и букв и не может начинаться с цифры. Значением может быть любой текст. Если значение содержит специальные символы, то его надо взять в кавычки. Присвоенное значение этих кавычек не содержит, естественно. Если переменная задана, то ее можно удалить, используя встроенную команду оболочки unset.
Набор всех установленных переменных оболочки с присвоенными им значениями называется окружением (environment) или средой оболочки. Вы можете просмотреть его с помощью команды set без параметров (только, может быть, следует организовать конвейер "set | less"). В выводе этой команды все переменные окружения перечисляются в алфавитном порядке. Для того чтобы просмотреть значение одной конкретной переменной, можно вместо команды set (в выводе которой нужную переменную еще искать и искать) можно воспользоваться командой
[user]$ echo $name
(правда, в этом случае вы должны знать имя интересующей вас переменной).
Среди переменных, которые вы увидите в выводе команды set, встречаются очень интересные переменные. Обратите, например, внимание на переменную RANDOM. Если вы несколько раз подряд выполните команду
[user]$ echo $RANDOM
вы каждый раз будете получать новое значение. Дело в том, что эта переменная возвращает случайное целое из интервала 0 — 32 768.
Shell как язык программирования
Как уже говорилось выше, для построения произвольных алгоритмов необходимо иметь операторы проверки условий. Оболочка bash поддерживает операторы выбора if … then … else и case, а также операторы организации циклов for, while, until, благодаря чему она превращается в мощный язык программирования.
Оболочка bash позволяет пользователю создавать
Оболочка bash позволяет пользователю создавать собственные функции. Функции ведут себя и используются точно так же, как обычные команды оболочки, т. е. мы можем сами создавать новые команды. Функции конструируются следующим образом:
function name () { list }
Причем слово function не обязательно, name определяет имя функции, по которому к ней можно обращаться, а тело функции состоит из списка команд list, находящегося между { и }. Этот список команд выполняется каждый раз, когда имя name задано как имя вызываемой команды. Отметим, что функции могут задаваться рекурсивно, так что разрешено вызывать функцию, которую мы задаем, внутри нее самой.
Функции выполняются в контексте текущей оболочки: для интерпретации функции новый процесс не запускается (в отличие от выполнения скриптов оболочки).
Скрипты оболочки и команда source
Скрипт оболочки — это просто файл, содержащий последовательность команд оболочки. Подобно функциям, скрипты можно выполнять как обычные команды. Синтаксис доступа к аргументам такой же, как и для функций.
В общем случае при запуске скрипта запускается новый процесс. Для того, чтобы выполнить скрипт внутри текущей сессии bash, необходимо использовать команду source, синонимом которой является просто точка ".". Скрипт оболочки служит просто аргументом этой команды. Ее формат:
source filename [arguments]
или
. filename [arguments]
Эта команда читает и выполняет команды из файла с именем filename в текущем окружении и возвращает статус, определяемый последней командой из файла filename. Если filename не содержит слэша, то пути, перечисленные в переменной PATH, используются для поиска файла с именем filename. Этот файл не обязан быть исполняемым. Если в каталогах, перечисленных в PATH, нужный файл не найден, его поиск производится в текущем каталоге.
Если заданы аргументы, на время выполнения скрипта они становятся позиционными параметрами. Если аргументов нет, позиционные параметры не изменяются. Значение (статус), возвращаемое командой source, совпадает со значением, возвращаемым последней командой, выполненной в скрипте. Если ни одна команда не выполнялась, или файл filename не найден, то статус выхода равен 0.
Специальные символы
Оболочка bash использует несколько символов из числа 256 символов набора ASCII в специальных целях, либо для обозначения некоторых операций, либо для преобразования выражений. В число таких символов входят символы:
` ~ ! @ # $ % ^ & * ( ) _ — [ ] { } : ; ' " / \ > <
а также символ с кодом 0, символ возврата каретки (генерируемый клавишей <Enter>) и пробел. В зависимости от ситуации эти специальные символы могут трактоваться либо в их специальном значении, либо в буквальном, т. е. как литералы. Но мы в основном будем предполагать, что все эти символы зарезервированы и не должны использоваться в качестве литералов. Это касается в первую очередь использования их в именах файлов и каталогов, о чем мы уже говорили в гл. 4. Однако символы _, - и . (знак подчеркивания, дефис и точка) часто используются в именах файлов, так что именно этот пример показывает, что специальное значение эти символы имеют не всегда. В именах файлов только символы точки (.) и слэша (/) имеют специальное значение. Символ слэша служит для разделения имен отдельных каталогов, а точка имеет специальное значение только если она является первым символом в имени файла (что означает, что файл является «скрытым»).
Давать сейчас точное определение того, какое специальное значение и в каких ситуациях имеет тот или иной специальный символ, нецелесообразно. Мы будем рассматривать их постепенно в следующих разделах, по мере того, как они потребуются. Однако есть три символа, которые имеют особое значение и которые поэтому необходимо рассмотреть в первую очередь.
Символ \ (обратный слэш) можно назвать "символом отмены специального значения" для любого из специальных символов, который стоит сразу вслед за \. Например, если мы хотим использовать символ пробела в имени файла, мы должны вместо простого пробела поставить \. Например, возможна следующая команда:
[user]$ cp two_words two\ words
Символы ' и " (одинарные и двойные кавычки) могут быть названы "символами цитирования". Любой из этих символов всегда используется в паре с его копией для обрамления какого-то выражения, совсем как в обычной прямой речи. Если какой-то текст взят в одинарные кавычки, то все символы внутри этих кавычек воспринимаются как литералы, никаким из них не придается специального значения. Если вернуться к тому же примеру с пробелами в имени файла, то можно сказать, что для того, чтобы дать файлу имя "two words" надо взять имя в кавычки:
[user]$ cp two_words 'two words'
Различие в использовании символов ' и " состоит в том, что внутри одинарных кавычек теряют специальное значение все символы, а внутри двойных кавычек — все специальные символы кроме $, ' и \ (знака доллара, одинарных кавычек и обратного слэша).
Текущий и домашний каталоги
Имя текущего каталога сохраняется в переменной окружения (с именем PWD), и значение этой переменной изменяется при каждом запуске программы cd (а также при смене текущего каталога любым другим способом, например, через Midnight Commander).
Аналогичным образом полное имя (с указанием пути) домашнего каталога пользователя, запустившего данный процесс, сохраняется в переменной HOME.
Удаление специальных символов
После того, как все подстановки в командной строке сделаны, из нее еще удаляются все вхождения символов \, ` и ", которые служили для отмены специального значения других символов.
Выполнение команд
Как было отмечено выше, одна из основных функций оболочки состоит в том, чтобы организовать исполнение команд пользователя, вводимых им в командной строке. В частности, оболочка предоставляет пользователю два специальных оператора для организации задания команд в командной строке: ; и &.
Замена тильды (Tilde Expansion)
Если слово начинается с символа тильды ('~'), все символы до первого слэша (или все символы, если слэша нет) трактуются как имя пользователя (login name).
Если это имя есть пустая строка (т. е. вслед за тильдой идет сразу слэш), то тильда заменяется на значение переменной HOME. Если значение переменной HOME не задано, тильда заменяется на полный путь к домашнему каталогу пользователя, запустившего оболочку.
Если вслед за знаком тильды (до слэша) стоит слово, совпадающее с именем одного из легальных пользователей, тильда и имя пользователя заменяются полным именем домашнего каталога этого пользователя. Если слово, следующее за тильдой, не является именем пользователя (и не пусто), то оно остается неизменным.
Если вслед за знаком тильды стоит ‘+’, эти два знака заменяются на полное имя текущего каталога (т. е. значение переменной PWD). Если за знаком тильды следует ‘-‘, подставляется значение переменой OLDPWD.