Командный интерпретатор поддерживает циклическую обработку. Чаще всего на практике используется цикл for - цикл по списку слов . Он описан в следующем подразделе.
Обратите внимание, что выделенные полужирным ключевые слова должны быть первым словом команды, т.е. первым словом в строке или идти сразу после точки с запятой.
Цикл for имеет следующий синтаксис:
<цикл 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 , представленная в следующем разделе.
Команда test имеет следующий синтаксис:
<команда test> ::=
test <выражение> | [ <выражение> ]
Выражение строится из примитивов, представленных в табл. 26 , при необходимости, с помощью следующих операторов:
! Унарный оператор отрицания. -a Бинарный оператор "и". -o Бинарный оператор "или". (<выражение>) Скобки для группировки. Учтите, что скобки распознаются командным интерпретатором, поэтому их надо брать в кавычки.
Таблица 26 . Основные примитивы команды test
Примитив Условие -r файл файл существует и доступен для чтения-w файл файл существует и доступен для записи-x файл файл существует и является выполняемым-f файл истина, если файл существует и является обычным файлом (не каталогом) -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 , устанавливающая с момента выполнения обработчик в виде последовательности команд (одним словом ) для всех перечисленных сигналов. Эта команда имеет следующий синтаксис:
<оператор trap> ::=
trap <последовательность команд> <список сигналов>
<список сигналов> ::=
<сигнал> {<пробелы> <сигнал>}
Рассмотрим пример реализации команды nohup , позволяющей запустить программу так, чтобы она продолжала работать при выключении терминала:
$ cat nohup
# nohup: no kill and hangup
trap "" 1 15
if test -t 2>&1
then
echo "Redirect stdout to 'nohup.out'"
exec nice -5 $* >>nohup.out 2>&1
else
exec nice -5 $* 2>&1
fi
$
Командный интерпретатор позволяет, при необходимости, запрашивать у пользователя информацию, которая помещается в указанную переменную. Для этого используется команда read :
$ read greeting
Hello, world!
$ echo $greeting
Hello, world!
$
На практике имеет смысл перед запросом выдать приглашение с помощью команды 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 требует, чтобы выражение было одним словом. Кроме того, для обращения к значению переменной в этой команде не нужно использовать метасимвол $ .
Одной из стандартных программ-калькуляторов является программа expr . Ее основные операторы представлены в табл. 27.
Таблица 27 . Основные операторы, распознаваемые командой expr
Оператор Результат выр1 \| выр2 Возвращает значение первого выражения, если оно не пустое и не равно 0, иначе, возвращает значение второго выражения. выр1 \& выр2 Возвращает значение первого выражения, если оба выражения - не пустые и не равны 0, иначе, возвращает 0. выр1 { +, - } выр2 Складывает или вычитает целочисленные аргументы. выр1 { \*, /, % } выр2 Умножает, делит или возвращает остаток от деления для целочисленных аргументов. length строка Возвращает длину строки.
Рассмотрим простой пример вычисления с помощью expr :
$ a=5
$ echo $a
5
$ a=`expr $a \* $a + 34 / 2`
$ echo $a
42
Обратите внимание, что между элементами выражения надо указывать пробелы.
Стандартным способом разбиения программ на модули в командном интерпретаторе является оформление необходимых действий в виде отдельного выполняемого файла с программой командного интерпретатора - создание новой команды. Тем не менее, для некоторых модулей такой подход может оказаться неэффективным и избыточным, так как модули могут не представлять самостоятельного значения вне программы, в которой они используются. Поэтому в современных версиях командных интерпретаторов предлагается возможность создавать и вызывать функции .
Синтаксис определения функции
Для определения функций используется ключевое слово function . Функции читаются и хранятся внутренне командным интерпретатором. Функции выполняются как команды, причем аргументы передаются как позиционные параметры . Синтаксис определения функции следующий:
<определение функции> ::=
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 ). Переменные, составляющие среду, в файле начального запуска надо экспортировать.
Рассмотрим пример содержимого файла начального запуска:
INFORMIXDIR=/usr/inf.731
INFORMIXSERVER=onarturo7
ONCONFIG=onconfig
SQLHOSTS=sqlhosts
PATH=$PATH:$INFORMIXDIR/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$INFORMIXDIR/lib/esql:$INFORMIXDIR/lib
DB_LOCALE=ru_ru.8859-5
CLIENT_LOCALE=ru_ru.8859-5
KAIOON=1
NODEFDAC=YES
DBDATE=DMY4/
DBTIME='%H:%M:%S %d/%m/%Y'
DBMONEY=.4
export KAIOON
export INFORMIXDIR INFORMIXSERVER LANG PATH ONCONFIG SQLHOSTS
export DBDATE DBTIME DBMONEY NODEFDAC
export DB_LOCALE CLIENT_LOCALE