Пользователь может определить функцию командного интерпретатора и использовать ее как встроенную функцию 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 имеет следующий синтаксис:
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 |
Более сложные выражения могут быть образованы с помощью логических операторов:
!выражение | Истинно, если выражение ложно (оператор NOT) |
выражение1 -а выражение2 | Истинно, если оба выражения истинны (оператор AND) |
выражение1 -o выражение2 | Истинно, если хотя бы одно из выражений истинно (оператор OR) |
Приведем несколько примеров использования выражений.
Фрагмент скрипта, используемый при регистрации нового пользователя. Скрипт проверяет наличие в домашнем каталоге инициализационного скрипта .profile и в случае его отсутствия копирует шаблон:
if [ ! -f $НОМЕ/.profile ]
then
echo "файла .profile не существует - скопируем шаблон"
cp /usr/lib/mkuser/sh/profile $НОМЕ/.profile
fi
Фрагмент скрипта, проверяющего наличие новой почты в почтовом ящике пользователя
if [ -s $MAIL ]
then
echo "Пришла почта"
fi
Фрагмент скрипта инициализации системы - запуска "суперсервера" Internet inetd(1M). Если исполняемый файл /etc/inetd существует, он запускается на выполнение.
if [ -х /etc/inetd ]
then
/etc/inetd
echo "запущен сервер inetd"
fi
Фрагмент скрипта, анализирующий ввод пользователя, сохраненный в переменной ANSW. Если пользователь ввел 'N' или 'n', скрипт завершает свою работу.
if [ "$ANSW" = "N" -о "$ANSW" = "n" ]
then
exit
fi
Циклы
Язык программирования Bourne shell имеет несколько операторов цикла. Приведем их синтаксис:
1) while условие
do
command1
command2
...
done
2) until условие
do
command1
command2
...
done
3) for var in список
do
command1
command2
...
done
С помощью оператора while команды command1, command2 и т.д. будут выполняться, пока условие не станет ложным. Как и в случае с оператором if, условие генерируется кодом возврата команды, например, test.
В случае оператора until команды command1, command2 и т.д. будут выполняться, пока условие не станет истинным.
Оператор for обеспечивает выполнение цикла столько раз, сколько слов в списке. При этом переменная var последовательно принимает значения, равные словам из списка. Список может формироваться различными способами, например как вывод некоторой команды (`имя_команды_формирующей_список`) или с помощью шаблонов shell.
В другой форме for, когда список отсутствует, переменная var принимает значения позиционных параметров, переданных скрипту.
Чтобы наглядно представить себе приведенные операторы, обратимся к конкретным примерам.
Например, скрипт монтирования всех файловых систем /etc/mounall для системы Solaris 2.5 включает в себя их проверку, исходя из данных, указанных в файле /etc/vfsck. При этом используется оператор while.
#
cat /etc/vfsck |
while read special fsckdev mountp fstype fsckpass automnt mntopts
# Построчно считывает записи файла vfsck и присваивает переменным spe-
# cial, fsckdev и т.д. значения соответствующих конфигурационных полей.
do
case $special in
'# ' * | '' ) # Игнорируем комментарии
continue ;;
'-') # Игнорируем строки, не требующие действия
continue ;;
esac
# Последовательно проверяем файловые системы с помощью утилиты
# /usv/sbin/fsck
/usr/sbin/fsck -m -F $fstype $fsckdev >/dev/null 2>&1
...
done
Скрипт очистки давно не используемых файлов во временных каталогах (обычно он запускается при загрузке системы) использует оператор for.
for dir in /tmp /usr/tmp /home/tmp
do
find $dir ! -type d -atime +7 -exec rm {} \;
done