Пользователь может определить функцию командного интерпретатора и использовать ее как встроенную функцию shell. С другой стороны, функции мало отличаются от скриптов, включая синтаксис и передачу аргументов. Однако являясь частью shell, функции работают быстрее.
Синтаксис функции имеет следующий вид:
function() {
command1
command2
...
}
Как можно заметить, телом функции является обычный скрипт shell.
В качестве примера приведем функцию mcd, позволяющую отобразить в приглашении shell имя текущего каталога.
mcd() {
cd $*
PS=`pwd`
}
Подстановки, выполняемые командным интерпретатором
Прежде чем выполнить команду, указанную либо в командной строке, либо в скрипте, командный интерпретатор производит определенную последовательность действий:
1. Анализирует синтаксис команды. В случае, если обнаружена синтаксическая ошибка, выводится соответствующее сообщение. Естественно, shell анализирует командную строку в соответствии с синтаксисом собственного языка, а не семантику вызова конкретной команды, например, наличие тех или иных аргументов.
2. Производит подстановки, а именно:
• Заменяет все указанные переменные их значениями. Например, если значение переменной var равно /usr/bin, то при вызове команды find $var -name sh -print переменная $var будет заменена ее значением. Другими словами, фактический запуск команды будет иметь вид:
find /usr/bin -name sh -print
• Формирует списки файлов, заменяя шаблоны. При этом производится подстановка следующих шаблонов:
* — соответствует любому имени файла (или его части), кроме начинающихся с символа '.',
[abc] — соответствует любому символу из перечисленных (а или b или с),
? — соответствует любому одиночному символу.
3. Делает соответствующие назначения потоков ввода/вывода. Если в строке присутствуют символы перенаправления (>, <, >>, <<, |), shell производит соответствующее перенаправление потоков. Программный интерфейс ввода/вывода мы рассмотрим в разделе "Работа с файлами" следующей главы.
4. Выполняет команду, передавая ей аргументы с выполненными подстановками. При этом:
• Если команда является функцией, определенной пользователем, вызывается функция.
• В противном случае, если команда является встроенной командой shell, запускается встроенная команда.
• В противном случае производится поиск программы в каталогах, указанных переменной $PATH, если имя команды задано без пути. Если имя команды задано явно, т.е. содержит элементы пути (относительный или абсолютный путь), производится запуск программы. В случае, если программа не найдена, выводится сообщение об ошибке.
Описанные подстановки, выполняемые интерпретатором, следует иметь в виду при запуске команд. Например, запуск команды rm приведет к удалению всех файлов данного каталога:
$ ls Вывести список файлов каталога
a.out client client.с
server server.с shmem.h
$ rm * Удалить файлы
$ ls
$ Каталог пуст
Команда rm(1) без колебаний выполнит свою функцию, поскольку в качестве аргументов она получит обычный список файлов. Замену символа '*' на список всех файлов каталога произведет shell, и rm(1) трудно догадаться, что вы собираетесь удалить все файлы. Реальный же вызов rm(1) будет иметь вид:
rm a.out client client.с server server.с shmem.h
Точно так же запускаемые программы ничего не знают о перенаправлении потоков ввода/вывода, произведенных командным интерпретатором. Напомним, что перенаправление ввода/вывода возможно лишь для стандартных потоков ввода, вывода и сообщений об ошибках. Впрочем, большинство утилит UNIX используют только стандартные потоки.
Как уже говорилось, запускаемые команды могут являться либо функциями, определенными пользователем, либо встроенными командами интерпретатора, либо исполняемыми файлами — прикладными программами и утилитами. В любом случае, синтаксис их вызова одинаков.
Если необходимо запустить сразу несколько команд, это можно сделать в одной строке, разделив команды символом ';'. Например:
$ pwd; date
Apr 18 1997 21:07
Заметим, что команды будут выполнены последовательно: сначала выполнится команда pwd(1), которая выведет имя текущего каталога, а затем date(1), которая покажет дату и время.
Можно запустить программу в фоновом режиме. В этом случае shell не будет ожидать завершения выполнения программы, а сразу выведет приглашение, и вы сможете продолжить работу в командном интерпретаторе. Для этого строку команды необходимо завершить символом '&':
$ find -name myfile.txt.1 -print >/tmp/myfile.list 2>/dev/null &
$
Пока утилита find(1) производит поиск файла с именем myfile.txt.1, сканируя файловую систему, вы сможете выполнить еще массу полезных дел, например, отправить почту или распечатать документ на принтере. Мы вернемся к этой схеме запуска программ далее в этой главе при обсуждении системы управления заданиями.
Наконец, командный интерпретатор предоставляет возможность условного запуска команд. Например, если необходимо выполнить команду только в случае успешного завершения предыдущей, следует воспользоваться следующей синтаксической конструкцией:
cmd1 && cmd2
В качестве примера рассмотрим поиск имени пользователя в файле паролей, и в случае успеха — поиск его имени в файле групп:
$ grep sergey /etc/passwd && grep sergey /etc/group
Успехом считается нулевой код возврата программы, неудачей — все другие значения.
Можно назначить выполнение команды только в случае неудачного завершения предыдущей. Для этого команды следует разделить двумя символами '|':
$ cmd1 || echo Команда завершилась неудачно
Приведенный синтаксис является упрощенной формой условного выражения. Командный интерпретатор имеет гораздо более широкие возможности проверки тех или иных условий, которые мы рассмотрим в следующем разделе.
Язык Bourne shell позволяет осуществлять ветвление программы, предоставляя оператор if. Приведем синтаксис этого оператора:
if условие
then
command1
command2
...
fi
Команды command1, command2 и т.д. будут выполнены, если истинно условие. Условие может генерироваться одной или несколькими командами. По существу, ложность или истинность условия определяется кодом возврата последней выполненной команды. Например:
if grep sergey /etc/passwd >/dev/null 2>&1
then
echo пользователь sergey найден в файле паролей
fi
Если слово sergey будет найдено программой grep(1) в файле паролей (код возврата grep(1) равен 0), то будет выведено соответствующее сообщение.
Возможны более сложные формы оператора if.
set `who -r`
Установим позиционные параметры равными значениям полей вывода программы who(1)
if [ "$9" = "S" ]
Девятое поле вывода — предыдущий уровень выполнения системы; символ 'S' означает однопользовательский режим
then
echo Система загружается
elif [ "$7" = "2" ]
Седьмое поле — текущий уровень
echo Переход на уровень выполнения 2
else
echo Переход на уровень выполнения 3
fi
Данный фрагмент скрипта проверяет уровень выполнения, с которого система совершила переход, и текущий уровень выполнения системы. Соответствующие сообщения выводятся на консоль администратора. В этом фрагменте условие генерируется командой test, эквивалентной (и более наглядной) формой которой является "[]". Команда test является наиболее распространенным способом генерации условия для оператора if.
Команда test имеет следующий синтаксис:
test выражение
или
[ выражение ]
Команда вычисляет логическое выражение (табл. 1.10) и возвращает 0, если выражение истинно, и 1 в противном случае.
Таблица 1.10. Выражения, используемые в команде test
Выражения с файлами -s
file Размер файла
file больше 0 -r
file Для файла
file разрешен доступ на чтение -w
file Для файла
file разрешен доступ на запись -x
file Для файла
file разрешено выполнение -f
file Файл
file существует и является обычным файлом -d
file Файл
file является каталогом -с
file Файл
file является специальным файлом символьного устройства -b
file Файл
file является специальным файлом блочного устройства -р
file Файл
file является поименованным каналом -u
file Файл
file имеет установленный флаг SUID -g
file Файл
file имеет установленный флаг SGID -k
file Файл
file имеет установленный флаг sticky bit Выражения со строками -z
string Строка
string имеет нулевую длину -n
string Длина строки
string больше 0
string1 =
string2 Две строки идентичны
string1 !=
string2 Две строки различны Сравнение целых чисел
i1 -eq
i2 i1 равно
i2 i1 -ne
i2 i1 не равно
i2 i1 -lt
i2 i1 строго меньше
i2 i1 -le
i2 i1 меньше или равно
i2 i1 -gt
i2 i1 строго больше
i2 i1 -ge
i2 i1 больше или равно
i2Более сложные выражения могут быть образованы с помощью логических операторов: