RFC (Request for Comments, Запрос на комментарии) - серия документов, публикуемая сообществом исследователей и разработчиков, руководствующихся практическими интересами, в которой описывается набор протоколов и обобщается опыт функционирования Интернет.
Командный интерпретатор поддерживает циклическую обработку. Чаще всего на практике используется цикл for - цикл по списку слов. Он описан в следующем подразделе.
Обратите внимание, что выделенные полужирным ключевые слова должны быть первым словом команды, т.е. первым словом в строке или идти сразу после точки с запятой.
for <имя переменной> [in <список слов>] do <команды> done
<список слов> ::=
<слово>{<пробел> <слово>}
<команды> ::=
<команда> {<; или перевод строки> <команда>}
Переменная последовательно получает значение очередного слова из списка, и для этого значения выполняются команды в теле цикла. Цикл завершается, когда пройден весь список слов. По умолчанию в качестве списка слов используются аргументы командной строки.
Рассмотрим пару примеров таких циклов:
$ for i in 1 2 3 4 5
> do
> echo $i
> done
Обратите внимание, что командный интерпретатор распознает цикл, выдает вторичное приглашение, и выполняет цикл только после его завершения ключевым словом done.
Список слов для цикла обычно порождается динамически. Например, путем раскрытия шаблонов имен файлов:
$ for i in *.c *.h
> do
> echo $i
> diff -b old/$i $i
> echo
> done | pr -h "diff `pwd`/old `pwd`" | lp &
[4] 1430
Можно также порождать его командой, подставляя ее результаты:
$ for i in `pick *.c *.h`
> do
> echo $i:
> diff -b old/$i $i
> done | pr | lp
Командный интерпретатор поддерживает также традиционные циклы по условию со следующим синтаксисом:
<оператор while> ::=
while <команды> do <команды> done
<оператор until> ::=
until <команды> do <команды> done
Выполняются команды, задающие условие, и проверяется код возврата последней из них. Если это ноль (истина), выполняются команды в теле цикла while или завершается выполнение цикла until. Если это не ноль (ложь), завершается работа цикла while или выполняется очередная итерация цикла until.
На основе этих циклов часто создаются программы-"следилки", работающие бесконечно:
$ cat watchfor
# watchfor: watching for log ins and log outs...
PATH=/usr/bin
new=/tmp/wfor1.$$
old=/tmp/wfor2.$$
>$old # создает пустой файл
while : # бесконечный цикл
do
who >$new
diff $old $new
mv $new $old
sleep 60
done | awk ' />/ { $1 = "in: "; print }
/ PRE < $ }? print ; $1="out: " {>
Командный интерпретатор поддерживает выполнение того или иного блока команд в зависимости от значения некоторого слова. Для этого предлагается оператор case со следующим синтаксисом:
<оператор выбора> ::=
case <слово> in <описание варианта> ) <команды> ;; {<описание варианта> ) <команды> ;; } esac
<описание варианта> ::=
<шаблон> { | <шаблон>}
<команды> ::=
<команда> {<разделитель> <команда>}
<разделитель> ::=
<перевод строки> | ;
Слово (обычно - значение переменной) сравнивается последовательно с шаблонами. Если произошло сопоставление (по правилам сопоставления шаблонов имен файлов) выполняются команды, соответствующие данному варианту и оператор завершается. Учтите, что шаблон *) сопоставляется с любым словом, и, тем самым, задает вариант по умолчанию.
В шаблонах оператора case символы . и /, в отличие от шаблонов имен файлов, не обязательно задавать явно.
Командный интерпретатор поддерживает условный оператор следующего общего вида:
<условный оператор> ::=
if <команды> then <команды> {elif <команды> then <команды>} [else <команды>] fi
Выполняются команды после if и проверяется код возврата последней из них. Если это 0 (истина) выполняются соответствующие команды после then и выполнение оператора завершается. Если же это не 0 (ложь), то при наличии конструкций elif выполняются последовательно соответствующие команды-условия и, если они возвращают код 0, команды после then, а затем оператор завершается. Если ни одно из условий не было истинным, выполняются команды в части else и оператор завершается.
В качестве условия в условном операторе может использоваться любая команда. Однако, имеется стандартная команда для проверки условий в традиционном понимании. Это команда test, представленная в следующем разделе.
истина, если файл существует и является обычным файлом (не каталогом)
-d файл
файл существует и является каталогом
-h файл
файл существует и является символьной связью
-s файл
файл существует и не пуст
-t [ дескриптор ]
истина, если открытый файл с указанным дескриптором (по умолчанию, 1) ассоциирован с терминалом
-z s1
истина, если строка s1 имеет нулевую длину
-n s1
истина, если строка s1 имеет ненулевую длину
s1 = s2
истина, если строки s1 и s2 идентичны
s1 != s2
истина, если строки s1 и s2 не совпадают
s1
истина, если строка s1 непустая
n1 -eq n2
сравнение целых чисел на равенство (=). Можно использовать также и другие сравнения: -ne (!=), -gt (>), -ge (>=), -lt (<) и -le (<=).
Рассмотрим пример использования условного оператора и команды test:
$ cat which
# which cmd: Безопасная версия сценария для выдачи каталога,
# из которого будет вызываться выполняемая программа
opath=$PATH
PATH=/usr/bin
# Это гарантирует использование настоящих команд
# echo, sed и test в любом случае!
case $# in
0) echo 'Usage: which command' 1>&2; exit 2
esac
for i in `echo $opath | sed 's/^:/.:/
s/::/:.:/g
s/:$/:./
s/:/ /g'`
do
if test -x $i/$1
then
echo $i/$1
exit 0 # команда найдена
fi
done
exit 1 # не найдена
$ which sed
./sed
$ which which
./which
В программах командного интерпретатора можно перехватывать и обрабатывать сигналы. Для этого используется команда trap, устанавливающая с момента выполнения обработчик в виде последовательности команд (одним словом) для всех перечисленных сигналов. Эта команда имеет следующий синтаксис:
Командный интерпретатор позволяет, при необходимости, запрашивать у пользователя информацию, которая помещается в указанную переменную. Для этого используется команда read:
На практике имеет смысл перед запросом выдать приглашение с помощью команды echo. Например, вот так:
$ cat pick
# pick: select arguments
PATH=/bin:/usr/bin
for i # for each argument, try $*, "$*" and "$@"
do
echo -n "$i? " > /dev/tty
read responce
case $responce in
y*) echo $i;;
q*) break
esac
done
Представленная выше программа pick выдает каждое указанное в качестве аргумента слово в отельной строке со знаком вопроса и требует от пользователя подтвердить необходимость его выдачи в стандартный выходной поток. Поскольку эта программа может использоваться в других сценариях, входной и выходной потоки которых перенаправлены, она взаимодействует непосредственно с текущим терминалом (через устройство /dev/tty).
Вычисления можно выполнять с помощью любой программы, воспринимающей свои параметры как выражение, значение которого необходимо вычислить, и выдающей результат вычисления в стандартный выходной поток. Одна из таких программ, expr, рассмотрена далее. Но современные командные интерпретаторы включают встроенную команду для выполнения простейших арифметических действий. Это команда let:
<команда let> ::=
let <аргумент> {<аргумент>}
Вот как ее можно использовать:
$ let a=5
$ echo $a
5
$ let a=a*a+34/2
$ echo $a
42
$ let "a = 7"
$ echo $a
7
Обратите внимание, что если вокруг знака равенства идут пробелы, необходимо брать выражение в кавычки. Команда let требует, чтобы выражение было одним словом. Кроме того, для обращения к значению переменной в этой команде не нужно использовать метасимвол $.
Стандартным способом разбиения программ на модули в командном интерпретаторе является оформление необходимых действий в виде отдельного выполняемого файла с программой командного интерпретатора - создание новой команды. Тем не менее, для некоторых модулей такой подход может оказаться неэффективным и избыточным, так как модули могут не представлять самостоятельного значения вне программы, в которой они используются. Поэтому в современных версиях командных интерпретаторов предлагается возможность создавать и вызывать функции.
Синтаксис определения функции
Для определения функций используется ключевое слово function. Функции читаются и хранятся внутренне командным интерпретатором. Функции выполняются как команды, причем аргументы передаются как позиционные параметры. Синтаксис определения функции следующий:
где список команд задает команды, выполняемые в качестве тела функции. Команды обычно разделяются точкой с запятой или переводами строк.
Выполнение и использование функций
Функции выполняются вызвавшим их процессом и используют все его файлы и текущий рабочий каталог. Сигналы, перехватываемые вызывающим процессом, внутри фунции обрабатываются стандартным образом. Сигналы, не перехватываемые или игнорируемые функцией, прекращают ее выполнение и передаются вызвавшей команде.
Обычно переменные совместно используются вызывающей программой и функцией. Однако, специальная команда typeset, используемая внутри функции, позволяет определять локальные переменные, область действия которых - текущая функция и все вызываемые ею функции.
Для выхода из функции используется специальная команда return. В случае ошибки в функции, управление передается вызывающей команде.
Идентификаторы определенных функций можно получить с помощью опций -f или +f специальной команды typeset. Текст функций показывается при использовании опции -f. Определение функции можно отменить с помощью опции -f специальной команды unset.
Обычно при выполнении сценария командным интерпретатором никакие функции не заданы. Опция -xf команды typeset позволяет экспортировать функцию для использования сценариями, выполняемыми без отдельного вызова интерпретатора. Функции, которые должны быть определены для всех вызовов интерпретатора, необходимо задавать в файле начального запуска с помощью опций -xf команды typeset.
Рассмотрим классический пример итеративной реализации функции вычисления факториала:
# test.sh - test shell functions
factorial () {
typeset i
typeset n
i=1; n=1
while [ $i -le $1 ]
do
let n=n*i
let i=i+1
done
echo $n
return
}
a=`factorial $11`
echo $a
При вызове эта программа, как и ожидалось, вычислит факториал своего первого параметра:
bash$ test.sh 5
120
Часто в виде функций оформляется выдача сообщений о параметрах вызова программы. В любом случае, если задача может быть разбита на подзадачи, решение этих подзадач имеет смысл оформлять в виде отдельной команды, если они полезны не только в контексте решаемой задачи, или в виде функции в противном случае.
Стандартная среда для работы командных интерпретаторов задается в файлах начального запуска, которые автоматически выполняются в начальном интерпретаторе с помощью команды точка (.), т.е. без порождения. Файлы начального запуска размещаются в начальном каталоге пользователя и называются .profile (sh, ksh) или .bash_profile (bash). Переменные, составляющие среду, в файле начального запуска надо экспортировать.
Рассмотрим пример содержимого файла начального запуска: