В дальнейшем изложении слово shell будет употребляться в двух смыслах - как имя
Для нас shell важен прежде всего как мощный язык
Свойства shell как интерактивного командного интерпретатора, непосредственно взаимодействующего
с пользователем, хранение и использование истории сеанса, возможности редактирования
Выделим основные понятия языка shell на лексическом уровне.
#, а также вся последующая часть строки
На синтаксическом уровне различаются несколько видов команд.
Значение простой команды - ее код завершения.
|
;, , или || ; и имеют равные приоритеты, меньшие, чем у и ||. Приоритеты последних также равны между собой. Символ ; означает, что - параллельно (т. е. shell не ожидает завершения ( || ) означает, что список, следующий за ней, будет выполняться лишь в том случае, если код завершения предыдущего ; можно использовать символ перевода строки.
Командная строка - строка текста на языке shell.
Для выполнения (почти) каждой простой команды shell порождает отдельный процесс, в рамках которого выполняется программа, хранящаяся в файле, заданном именем команды. Программа может быть выполнимой, т. е. содержать машинные инструкции, или представлять собой shell-процедуру - содержать текст на языке shell.
Далее при описании синтаксиса конструкций языка shell и способа вызова служебных программ будут использоваться следующие соглашения:
Например, утилита ls не подсчитывает число файлов в каталоге, а лишь выдает информацию о них. С другой стороны, служебная программа wc способна подсчитать число строк в файле, но не имеет отношения к распечатке содержимого каталогов. Если же построить wc, вызванная без аргументов, обрабатывает файл стандартного ввода, который в данном случае является результатом работы команды ls ). Значит, в каталоге /bin 91 файл, если считать и элементы, соответствующие текущему и вышележащему каталогам (первая строка выдачи ls содержит суммарное число блоков, занятых файлами каталога).
ls -al /bin | wc -l
Еще один пример. Пусть необходима информация о файлах текущего каталога, модифицированных в октябре. К цели ведет
ls -al | grep "Oct "
Служебная программа grep играет здесь роль ls.
Можно выстроить и трехступенчатый
ls -al | grep "Oct " | wc -l
Здесь утилиту grep с еще большим правом можно назвать фильтром.
Приведем еще один пример
ls -Rl /dev | more
tee файл ...
Например, если нужно не только подсчитать число файлов из текущего каталога, модифицированных в октябре, но и поместить информацию о них в файл для последующего анализа, следует построить четырехступенчатый конвейер (см. листинг 2.5):
ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l
В результате его выполнения на экране появится сообщение о количестве нужных файлов, а в файле /tmp/tmpinf - информация о них.
Переменные обозначаются именами. Значения могут присваиваться им привычным способом, то есть посредством команд вида:
имя=значение [имя=значение] ...
Все значения в языке shell трактуются как текстовые. Подчеркнем, что, в соответствии с этими требованиями, конструкция
имя=значение
должна представлять собой одно слово - в ней не может быть пробелов.
Обычно в языках программирования ясно из контекста, где подразумевается имя переменной, а где значение. Так, в левой части оператора присваивания обычно используется имя, в правой - значение. В shell все не так. Переход от имени переменной к значению помечается посредством явной операции $. Если в команде встречается конструкция
$имя
то вместо нее интерпретатор shell подставляет значение переменной с указанным именем. Допускается и запись
${имя}
с тем же смыслом, если нужно отделить имя от последующего текста.
Рассмотрим пример. После выполнения команд (см. листинг 2.6) - утилита echo (эхо) выдает на стандартный вывод свои аргументы - на экране появится результат вывода значений переменных (см. листинг 2.7).
a=value_of_variable b=1+2 echo a = $a echo b = $b
a = value_of_variable b = 1+2
Значения формальных аргументов shell-процедур обозначаются как
$цифра
$0 - это имя интерпретируемой shell-процедуры. Если заданных при вызове команды фактических аргументов меньше, чем 9, "лишние" формальные аргументы получают пустые значения. О том, как добраться до фактических аргументов с номерами большими, чем 9, будет сказано ниже (см. управляющую конструкцию for и команду shift ).
В качестве примера рассмотрим shell-процедуру, которая выдает на стандартный вывод свое имя и значения трех первых аргументов (см. листинг 2.8).
echo Имя команды: $0 echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Пусть приведенный текст помещен в файл с именем three_args. Тогда после выполнения команды (см. листинг 2.9) на экране появится ее результат (см. листинг 2.10).
three_args arg1 . - arg4
Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Поскольку в тексте shell-процедуры упомянуты только первые 3 аргумента, значения аргументов с большими номерами (даже если они заданы, как в приведенном примере) не влияют на ее работу.
Команда
three_args arg1 --
выдаст другой результат (см. листинг 2.11):
Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: -- Значение третьего аргумента:
Значение третьего формального аргумента пусто.
Прежде чем начнется выполнение командной строки, т. е. будет вызвана заданная в ней команда, строку обрабатывает shell, выполняющий в ней некоторые подстановки. С одной из них - значения переменной вместо конструкции $имя - мы уже познакомились. Язык shell содержит ряд аналогичных конструкций. Рассмотрим их.
Там, где в командной строке встречается запись вида
${имя:-слово}
вместо нее подставляется значение переменной с указанным именем, если это значение непусто, в противном случае подставляется слово. Например, при наличии в shell-процедуре присваивания
initdir=${1:-/}
переменная initdir получит значение первого аргумента, если оно непусто. Если же процедуру вызвали без аргументов, значением initdir станет символ /. Подобные конструкции - удобный способ использования подразумеваемых значений.
Запись вида
${имя:=слово}
влечет присваивание нового значения (слово) переменной с заданным именем, только если ее старое значение не установлено или пусто (удобный способ присваивания переменным подразумеваемых значений). Вместо всей конструкции в командную строку подставляется итоговое (непустое старое или присвоенное новое) значение переменной.
Конструкция
${имя:?слово}
предназначена для выдачи в стандартный протокол сообщения об ошибке, если значение переменной с заданным именем не установлено или пусто, после чего неинтерактивный shell завершает работу с ненулевым кодом возврата. Непустое значение подставляется в командную строку, и интерпретация командного файла продолжается обычным образом.
Вместо конструкции
${имя:+слово}
подставляется слово, если значение переменной с заданным именем непусто; в противном случае не подставляется ничего. Такой способ удобен для использования альтернативных значений.
Если в приведенных выше конструкциях опустить двоеточие, будет отменена проверка непустоты значения (останется лишь проверка того, установлено ли значение переменной с заданным именем. В остальном смысл конструкций остается прежним.
Shell содержит базовые средства для обработки цепочек символов. Так, вместо конструкции
${#имя}
подставляется число символов в значении переменной с заданным именем.
Предоставляется четыре конструкции для
${имя%слово}
${имя%%слово}
${имя#слово}
${имя##слово}
Во всех случаях слово рассматривается как образец (
Приведем несколько примеров. Для их понимания достаточно знать, что шаблон * сопоставляется с произвольной цепочкой символов, в том числе с пустой.
После присваивания переменной
HOME=/home/galat
команда
echo ${#HOME}
выдаст 11.
Заменить в имени файла суффикс .f на .for можно с помощью команд, показанных в листинге 2.12:
f=file.f
mv $f ${f%.f}.for
Выдать первый элемент маршрутного имени файла (см. далее) можно с помощью команд, показанных в листинге 2.13:
f=маршрутное_имя
echo ${f%%/*}
Последний элемент выдается командой, приведенной в листинге 2.14:
echo ${f##*/}
Значения некоторых переменных устанавливаются самим языком shell. Перечислим эти переменные и опишем их предназначение.
#
Количество фактических аргументов (десятичное).
-
Флаги (однобуквенные set (см. далее).
?
Десятичное значение, возвращенное предыдущей
$
!
Идентификатор последнего
*, @
Совокупность всех фактических аргументов (начиная с $1 ), разделенных пробелами.
Напомним: чтобы получить значения этих переменных, перед ними нужно поставить знак $.
Значения $@ и $* имеют некоторые тонкие различия, на которых мы, однако, останавливаться не будем.
Несколько усложним процедуру three_args, чтобы продемонстрировать только что описанные возможности (см. листинг 2.15).
echo Идентификатор текущего процесса: $$ echo Имя команды: $0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Теперь, вызывая усовершенствованную процедуру three_args, выполним командную строку
three_args arg1 . - arg4
Тогда на экране появится примерно следующее (см. листинг 2.16):
Идентификатор текущего процесса: 3882 Имя команды: three_args Число фактических аргументов: 4 Совокупность всех аргументов: arg1 . - arg4 Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Окружение - это набор пар (имя, значение), который передается выполняемой программе так же, как и обычный список аргументов. Иными словами, порождаемые процессы наследуют
HOME
Подразумеваемый аргумент утилиты cd -
IFS
Цепочка символов, где перечислены
PATH
PS1
Основное приглашение $ ").
TERM
Тип пользовательского терминала.
TZ
Информация о часовом поясе.
Для отсылки информации об окружении на стандартный вывод следует воспользоваться командой
env
Поясним подробнее смысл некоторых переменных окружения.
Прежде чем выполнить команду, shell ищет файл с соответствующим именем в последовательности каталогов, являющейся значением переменной PATH. Назовем подобную последовательность списком поиска. Если, например, значение $PATH суть
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
то нужный файл будет сначала разыскиваться в каталоге /usr/local/bin, затем в /bin и т.д. Как только файл отыщется, поиск прекратится. Это важно, если в разных каталогах есть одноименные выполнимые файлы.
Значение $IFS влияет не только на разбор команд, но и на чтение строк данных с помощью команды read (см. далее). Кроме того, первый символ из значения $IFS вставляется между фактическими аргументами при выполнении подстановки $*.
Переменная TERM хранит тип терминала пользователя. Интерактивные утилиты (редакторы или другие программы с talk ) с помощью значения $TERM настраиваются на конкретный тип терминала.
Переменная TZ задает локальный часовой пояс. Эта информация необходима всегда, когда требуется показать текущее время. Особенно полезна она при
Для изменения окружения мало присвоить новое значение соответствующей переменной. Дело в том, что по умолчанию переменные считаются локальными по отношению к shell-процедуре, т. е. присваивание изменит локальную переменную, но не затронет одноименную переменную окружения. Таким образом, в окружение новых процессов (порожденных, например, для выполнения последующих команд данной shell-процедуры) войдет переменная со старым значением.
С помощью конструкции
export имя[=слово]
переменная с указанным именем и значением (последнее может быть опущено) помещается в окружение, т. е. становится глобальной.
Следующая строка иллюстрирует типичное применение команды export:
export PATH=/local/bin:$PATH
Переменная PATH изменилась, и новое значение экспортировано в окружение.
Команда
export -p
выдает на стандартный вывод имена и значения всех экспортированных переменных. Эту выдачу можно использовать для сохранения и последующего восстановления (быть может, с некоторыми модификациями) окружения. Ниже приведен фрагмент возможного результата работы команды export -p (см. листинг
2.17).
export HISTSIZE="1000" export HOME="/home/galat" export LANG="C" export LESSCHARSET="koi8-r" export LOGNAME="galat" export MAIL="/var/spool/mail/galat" export TTY="/dev/ttyS4" export USER="galat"
Мы видим, что выдача устроена так, чтобы вновь быть поданной на вход командного интерпретатора.
Если в командной строке встретилась цепочка символов, заключенная в обратные кавычки ( ` ), она интерпретируется как команда, стандартный вывод которой подставляется вместо упомянутой конструкции. Говорят, что в этом случае производится
Эквивалентной синтаксической формой для подстановки результата выполнения команды является конструкция вида
$(команда)
Для выполнения заданной команды порождается процесс, в рамках которого работает еще один экземпляр командного интерпретатора.
Опишем несколько употребительных способов использования filelist содержит список имен других файлов и над совокупностью последних требуется проделать
некоторое действие. Если в командную строку поместить конструкцию
... `cat filelist` ...
то это аналогично явному указанию в командной строке на все файлы из списка. (Утилита cat выдает на стандартный вывод содержимое файлов, заданных в качестве аргументов.) Пример
ls -l `cat filelist`
Нет нужды в том, чтобы каждая служебная программа умела читать свои аргументы из файла: имеются универсальный механизм cat.
${x:-$(ls)}
команда ls выполнится только тогда, когда значение x не установлено или пусто; в противном случае выполнится команда $x, и ее результат займет место в командной строке.
В языке shell все значения считаются текстовыми. Значит, для выполнения операций с числами нужны особые средства. Вместо конструкции вида
$((выражение))
shell подставит результат
i=$(($i+1))
значение i (если оно было числом) увеличится на единицу.
Стандарт POSIX обязывает поддерживать арифметику длинных целых со знаком; константы (десятичные, восьмеричные, шестнадцатеричные) должны удовлетворять требованиям языка C.
Среди прочих язык shell содержит следующие управляющие конструкции.
Оператор for:
for имя [in слово ...] do список done
При каждой итерации переменная имя принимает следующее значение из набора
in слово ...
Если конструкция in слово ... опущена, то список выполняется для каждого формального аргумента.
Условный оператор:
if список_1 then список_2 [elif список_3 then список_4] ... [else список_5] fi
Выполняется список_1. Если код его завершения 0, то выполняется список_2, в противном случае - список_3, и если код его завершения 0, то выполняется список_4 и т.д. Если же коды завершения всех списков, использованных в качестве условий, оказались ненулевыми, выполняется else -часть ( список_5 ). Если else -часть отсутствует и ни одна then -часть не выполнялась, возвращается нулевой код завершения.
Оператор цикла while ( until ):
while список_1 do список_2 done
Пока код завершения последней команды списка_1 есть 0, выполняются команды списка_2. При замене служебного слова while на until списка_2 не выполнялись вообще, код завершения устанавливается равным нулю.
Оператор выбора:
case слово in [шаблон [| шаблон] ...) список ;;] ... esac
Выполняется список, соответствующий первому из шаблонов, успешно сопоставленных со словом. Формат шаблона аналогичен используемому для генерации имен файлов (см. далее).
имя () открывающая_скобка список закрывающая_скобка
Определяется
имя [аргумент ...]
Если тело функции заключено в фигурные скобки, она выполняется в рамках текущего процесса; в случае круглых скобок порождается отдельный процесс. На время выполнения функции $1, $2, ..., а также служебная переменная # получают новые значения, определенные аргументами команды вызова. Затем восстанавливаются старые значения.
Приведем примеры использования управляющих конструкций. Сначала усовершенствуем процедуру three_args, чтобы она выдавала значения всех, а не только первых трех аргументов (см. листинг 2.18):
echo Идентификатор текущего процесса: $$
echo Имя команды: $0
echo Число фактических аргументов: $#
echo Совокупность всех аргументов: $@
i=1
for arg
do
echo Значение аргумента номер ${i}: $arg
i=$(($i+1))
done
В управляющих конструкциях if и while часто используется команда test, она проверяет некоторое условие и вырабатывает нулевой или ненулевой код завершения в зависимости от того, оказалось ли условие, соответственно, истинным или ложным. Команда записывается любым из двух способов:
test условие
или употребив пару квадратных скобок:
[ условие ]
Условие может включать, помимо прочих, следующие примитивы:
-r файл
Истина, если файл существует и доступен для чтения.
-w файл
Истина, если файл существует и доступен для записи.
-x файл
Истина, если файл существует и является выполнимым.
-f файл
Истина, если файл существует и является обычным файлом.
-d файл
Истина, если файл существует и является каталогом.
-c файл
Истина, если файл существует и является специальным символьным файлом.
-b файл
Истина, если файл существует и является специальным блочным файлом.
-p файл
Истина, если файл существует и является именованным каналом.
-s файл
Истина, если файл существует и имеет ненулевой размер.
-n s1
Истина, если цепочка символов s1 имеет ненулевую длину.
s1 = s2
Истина, если цепочки символов s1 и s2 равны.
s1 != s2
Истина, если цепочки символов s1 и s2 не равны.
n1 -eq n2
Истина, если целые числа n1 и n2 алгебраически равны. На месте -eq могут быть также операции сравнения -ne (не равно), -gt (больше), -ge (больше или равно), -lt (меньше), -le (меньше или равно).
Примитивы комбинируются с помощью круглых скобок, а также следующих операций (в порядке уменьшения приоритета):
| ! | Унарная |
-a |
|
-o |
Обратим внимание на то, что квадратные скобки, обрамляющие условие, и каждый компонент условия должны быть выделены пробелами.
Приведем пример использования управляющей конструкции if. В процессе загрузки практически любой разновидности ОС Unix выполняются строки следующего или близкого вида (см. листинг 2.19):
if [ -s ${f} ]
then
/bin/sh ${f} start
fi
Если файл, имя которого является значением переменной f, существует и имеет ненулевой размер, он выполняется с аргументом start.
В качестве примера употребления конструкции case приведем еще один фрагмент, типичный для процесса загрузки системы (см. листинг 2.20):
case "$1" in
start)
start
;;
stop)
stop
;;
reload | restart)
restart
;;
condrestart)
if [ -f /var/lock/subsys/atd ]
then
restart
fi
;;
*)
echo $"Usage: $0 {start | stop | restart | condrestart}"
exit 1
esac
Известные значения первого аргумента распознаются, в ответ на все прочие (шаблон *) сообщается, как пользоваться данной shell-процедурой.
Следующий пример иллюстрирует определение и вызов функции (см. листинг 2.21).
echo $# $1
f ( ) {
echo $# $1
}
f a b
f b
echo $# $1
Если данный текст хранится в файле g, то по окончании выполнения команды (вызов shell-процедуры, содержащей функцию)
g c d e
будет выдан следующий результат:
3 c 2 a 1 b 3 c
После всех подстановок, прежде чем команда начнет выполняться, в каждом составляющем ее поле осуществляется поиск символов ?, и . в начале имени файла или непосредственно после ? и [:
* - сопоставляется с произвольной цепочкой символов, в том числе с пустой. |
? - сопоставляется с произвольным символом. | !, шаблону удовлетворяет любой символ, не перечисленный в скобках. |
Рассмотрим несколько примеров. Чтобы подсчитать суммарное число строк во всех C -файлах текущего каталога, достаточно выполнить команду
wc -l *.c
В качестве второго примера расширим приведенный выше фрагмент действий при загрузке системы (см. листинг 2.22):
for f in /etc/rc$runlevel.d/S*
do
if [ -s ${f} ]
then
/bin/sh ${f} start
fi
done
В цикле будут в алфавитном порядке запускаться все непустые файлы из каталога, соответствующего заданному уровню выполнения (обычно это /etc/rc3.d, поскольку имеет значение 3, но в данном случае это не суть важно), имена которых начинаются на S.
Как пример использования шаблона имен файлов приведем команду
rm -f .*.[Bb]?
Она удалит из текущего каталога все файлы, имена которых начинаются с точки, сразу после второй точки стоит одна из букв - B или b, а затем произвольный символ.
Читателю предлагается рассмотреть еще один способ применения шаблона имен файлов и самостоятельно ответить на вопрос, удалит ли команда
rm -f *.[Bb][AaUu]
файл с именем .bu.
Наконец, рассмотрим более сложный пример. Пусть требуется так переименовать фортрановские файлы текущего каталога, чтобы окончание .f заменилось на .for. Служебная программа
basename цепочка_символов [суффикс]
убирает из цепочки_символов любой оканчивающийся на / префикс и суффикс (если он есть) и выдает результат на стандартный вывод. Применим ее для решения сформулированной задачи (см. листинг 2.23).
for f in *.f do mv $f `basename $f .f`.for done
Особенностью приведенного примера является использование
Еще одним видом генерации маршрутных имен файлов можно было бы считать обработку символа ~ ', однако это действие открывает, а не завершает подстановки в командной строке. Префикс слов, от тильды до ближайшего слэша (или конца слова, если слэш отсутствует), заменяется на имя домашнего каталога пользователя, HOME. В присваиваниях тильда распознается и обрабатывается не только в начале слова, но и после знаков равенства и двоеточия, что позволяет, в частности, естественным образом обращаться со значением переменной окружения PATH.
Так, при подстановке результатом работы команды echo ~ может быть /home/galat.
Командный интерпретатор shell ассоциирует с каждым открытым файлом так называемый дескриптор. Дескрипторы нумеруются десятичными целыми числами, начиная с нуля. Верхняя граница зависит от реализации, но, согласно стандарту POSIX, должны поддерживаться по крайней мере десять одновременно открытых файлов (с
Дескрипторы с номерами 0, 1 и 2 имеют специальный смысл. Они соответствуют стандартному вводу, стандартному выводу и стандартному протоколу.
Перед выполнением команды ее ввод и вывод могут быть перенаправлены, для чего используется специальная нотация, которую интерпретирует shell. Описанные ниже конструкции располагаются в любом месте простой команды, предшествуют команде или завершают ее и не передаются в качестве аргументов команды; если они завершают определение функции, то воздействуют на все команды тела, не производящие собственных перенаправлений.
Перенаправление ввода:
<слово
Использовать файл слово для стандартного ввода (
Перенаправление вывода с перезаписью:
>слово >|слово
Использовать файл слово для стандартного вывода (дескриптор файла 1). Имеющийся файл опустошается, а если файла нет, он создается. (При использовании первой разновидности -C команды set.)
Перенаправление вывода с дозаписью:
>>слово
Использовать файл слово для стандартного вывода. Если файл существует, то выводимая информация добавляется в конец (сначала производится поиск конца файла); в противном случае файл создается. Если любой из этих конструкций предшествует цифра, она определяет дескриптор (вместо подразумеваемых дескрипторов 0 или 1), который будет ассоциирован с файлом, указанным в конструкции. Например, строка
... 2>protocol
перенаправляет стандартный протокол (дескриптор 2) в файл по имени protocol.
Ввод и вывод могут перенаправляться не только в файл с заданным именем, но и в открытый файл с заданным дескриптором. Для этого в описанных выше конструкциях в качестве слова следует употребить связку номер_дескриптора:
<номер_дескриптора >номер_дескриптора
Например, чтобы перенаправить стандартный протокол туда же, куда назначен стандартный вывод, употребить конструкцию
... 2>1
Если в качестве номера_дескриптора указан минус (' - '), соответствующий файл (стандартный ввод, вывод или явно заданный своим дескриптором файл) закрывается.
Shell позволяет открыть файл одновременно на чтение и запись при помощи конструкции
<>слово
Ей, как всегда, может предшествовать номер дескриптора для открываемого файла. По умолчанию используется 0, то есть стандартный ввод.
Приведем два примера. Пусть нужно измерить время выполнения некоторой команды, направив ее результаты в файл cmd.res, а данные о времени - в файл cmd.time. К цели ведет строка, приведенная в листинге 2.24:
time команда >cmd.res 2>cmd.time
Второй пример. Рассмотрим цикл, описанный в листинге 2.25:
i=0 while [ $i -lt 40 ] do > lost+found/g$i i=$(($i+1)) done rm lost+found/g*
С его помощью создается 40 файлов в каталоге lost+found, которые затем удаляются. Отметим, что перенаправляется стандартный вывод пустой команды, а в результате создается пустой файл.
(Поясним смысл приведенного фрагмента. При проверке и коррекции файловой системы утилитой fsck в каталог /lost+found помещаются непустые файлы, на которые нет ссылок. Сложность в том, что пока утилита fsck работает, ни один файл не должен расширяться, т. е. в каталоге /lost+found должны быть заранее заготовленные пустые места.)
Shell дает возможность размещать исходные данные в виде
<<слово вставка слово
и
<<-слово вставка слово
Их можно считать разновидностями перенаправления ввода. На вход команды подается вставка, представляющая собой череду строк вплоть до слова, указанного при перенаправлении. Второй вариант отличается тем, что из строк вставки удаляются начальные символы табуляции.
В качестве примера использования вставок приведем файл ОС Linux /etc/tripwire/twinstall.sh, который содержит следующие фрагменты (см. листинг 2.26 и листинг 2.27):
cat << END_OF_TEXT A clear-text version of the Tripwire configuration file $TXT_CFG has been preserved for your inspection. It is recommended that you delete this file manually after you have examined it. END_OF_TEXT
cat <<eof1; cat <<eof2 Вставка 1 eof1 Вставка 2 eof2
В результате выполнения второго фрагмента будут выданы две строки:
Вставка 1 Вставка 2
| ; < > ( ) $ ` \ " ' пробел перевод_строки
Не являясь разделителями слов, особую роль играют для shell и символы
* ? [ ] # ~ = %
Каждый из перечисленных выше символов может быть экранирован, т. е. представлять самого себя, если перед ним стоит \ либо он расположен между одиночными или двойными кавычками ( '' или "" ).
Экранирование с помощью двойных кавычек имеет некоторые особенности. Во-первых, оно не отменяет особой трактовки символов $ и `. Иными словами, внутри двойных кавычек обычным образом выполняется подстановка значений переменных, результатов выполнения команд и результатов вычисления арифметических выражений, только результат подстановки расценивается как одно слово. Во-вторых, особая трактовка символа \ отменяется внутри двойных кавычек лишь частично: символ \ сохраняет экранирующую роль, если после него следуют:
$ ` " \ перевод_строки
Приведем несколько примеров использования двойных кавычек для экранирования. В первом команда
echo "$(echo *)"
выдаст список файлов текущего каталога, а во втором результатом выполнения строки
echo "$(echo "*")"
станет звездочка. Если же аргумент " $(echo *) " предложить усовершенствованной процедуре three_args, то число фактических аргументов окажется равным единице, а список файлов текущего каталога станет значением первого аргумента.
Рассмотрим третий пример. Пусть нужно выдать информацию о всех файлах текущего каталога, которые модифицировались последний раз 3 октября.
ls -al | grep "Oct 3"
сделает то, что нужно (служебная программа grep выполняет сопоставление заданного шаблона со строками заданных файлов или стандартного ввода). Кавычки использованы для экранирования двух пробелов в разыскиваемой цепочке символов. Без экранирования пробелы превращаются в разделители аргументов, и команда grep попыталась бы найти текст Oct в файле с цифровым именем 3.
Еще один пример. Запись
$(($i\*5))
синтаксически неверна, поскольку в арифметических выражениях shell рассматривает звездочку как операцию умножения, а не как элемент шаблона имен файлов; соответственно, она не нуждается в экранировании, и символ \ становится лишним, нарушая синтаксис выражения. Таким образом, лишнее экранирование может быть вредным.
Мы уже отмечали, что подавляющее большинство команд разыскивается в списке поиска и выполняется языком shell в рамках порожденных процессов. Однако для некоторых команд - либо по соображениям эффективности, либо потому, что требуемую функциональность трудно достичь в рамках отдельного процесса - целесообразно сделать исключение и реализовать их как встроенные, их выполняет сам shell. Очевидным примером export - пометить переменную как глобальную, входящую в
Встроенные команды подразделяются на обычные и специальные. С точки зрения приложений первые ничем (кроме, быть может, эффективности обработки) не отличаются от невстроенных утилит. В частности, они могут фигурировать в качестве программы процесса. Специальные встроенные команды этим свойством обладать не обязаны. Кроме того, у них есть еще два отличия:
Перечислим некоторые из
. файл
Shell читает и выполняет команды из файла, затем возобновляется чтение со стандартного ввода; при поиске файла используется значение переменной . позволяет выполнить любую команду в рамках текущего процесса.
eval [аргумент ...]
Выполнить команду, заданную аргументами eval.
exec [аргумент ...]
exec, заменяет shell. В качестве аргументов могут указываться спецификации ввода/вывода, и, если нет никаких других аргументов, будет лишь перенаправлен ввод/вывод текущего процесса shell.
exit [код_завершения]
Завершить выполнение shell с указанным кодом. При отсутствии аргумента код завершения определяется последней выполненной командой. Чтение символа конца файла также приводит к завершению shell.
shift [n]
Формальные аргументы, начиная с ( n+1 )-го, переименовываются в $1 и т.д. По умолчанию n=1.
К числу
cd [каталог]
Сделать текущим заданный каталог. Если каталог не указан, используется значение переменной окружения HOME.
pwd
Выводит имя текущего каталога.
read [переменная ...]
Со стандартного ввода читается одна строка и делится на поля; первое поле присваивается первой переменной, второе - второй и т.д., причем последовательность всех оставшихся полей присваивается последней переменной. Исходная строка имеет продолжение, если в конце ее стоит последовательность \перевод_строки. Символы, отличные от перевода строки, также могут быть экранированы с помощью \, который удаляется перед присваиванием полей. Возвращается нулевой код завершения, если только не встретился конец файла.
Некоторые пояснения. С помощью команды exec осуществляется смена программы текущего процесса. Например, если пользователь привык работать в командном интерпретаторе bash, он может перейти туда по команде
exec bash
При этом обычный shell бесследно исчезнет.
Аргументы eval обрабатываются дважды: когда исходную строку интерпретирует shell и когда работает сама eval. Так, после выполнения строк
a=1 b=a eval c='$'$b echo $c
на стандартный вывод будет выдана единица, поскольку аргументом eval будет оператор присваивания c=$a.
Если файл data содержит строку
1 2 3 4 5
то после выполнения read
read a b c echo $c $b $a
на стандартный вывод будет выдан следующий результат:
3 4 5 2 1
Текст " 3 4 5 " (остаток строки) стал значением переменной c.
Команду shift используют при последовательной обработке аргументов shell-процедур. Например, если требуется выдать аргументы, которые являются именами существующих обычных файлов, употребляют цикл вида (см. листинг 2.28):
while [ "$1" ] do [ -f $1 ] echo $1 shift done
Важную роль играет еще одна специальная встроенная команда - set. Она используется для изменения режима работы shell и для присваивания новых значений формальным аргументам. Сразу же отметим, что наивное присваивание
цифра=слово
shell трактует как запуск программы с именем цифра=слово, т. е. оно не позволяет изменить значение формального аргумента.
Команда set записывается в виде
set [опция ...] [аргумент ...]
Из ее опций упомянем следующие:
-a
-C
Защищать существующие файлы от перезаписи при > " (см. выше раздел "Перенаправление ввода/вывода").
+o
Выдать на стандартный вывод текущие опции языка shell в виде команд, которые устанавливают те же значения и которые можно подать ему на вход.
-x
Устанавливает режим +x выключает трассировку. Стандарт не специфицирует, будет ли трассироваться команда set +x.
Если в команде set заданы аргументы, они станут новыми значениями $1, $2 и т.д. Типичный пример использования команды set содержится в одном из файлов ОС Linux ( /etc/rc.d/rc ) выполняемых при загрузке системы (см. листинг 2.29):
argv1="$1" set `/sbin/runlevel` runlevel=$2 previous=$1
Возможностью, не обязательной с точки зрения стандарта POSIX-2001 (отнесенной к расширению "Мобильность пользователей", UP), но весьма полезной на практике, является механизм alias:
alias [имя[=синоним] ...]
Когда shell выделяет в командной строке слово, расположенное вместо имени простой команды, он проверяет, не является ли оно именем синонима, и при положительном ответе заменяет его значением, заданным командой alias и представляющим собой цепочку символов.
Например, если хочется, чтобы команда ls по умолчанию выдавала информацию о файлах, имена которых начинаются с точки, можно воспользоваться спецификацией для определения синонима:
alias ls="ls -a"
Если подставленный синоним оканчивается пробелом, то анализируется следующее слово командной строки; если и оно оказывается именем синонима, подстановки продолжаются. Например, если синоним для ls задать в виде строки с завершающим пробелом
alias ls="ls -a "
и дополнительно ввести еще одно определение синонима
alias l="-Rl"
то команда, использующая синоним с завершающим пробелом,
ls l
будет раскрыта в строку
ls -a -Rl
и выдаст в длинном формате информацию о всех файлах текущего каталога и его подкаталогов.
Команда alias без аргументов выдает список имеющихся синонимов.
Вызов командного интерпретатора shell осуществляется командой
sh [опция...] [командный_файл [аргумент ...]]
или
sh -c [опция...] командная_цепочка
[имя_команды [аргумент ...]]
или
sh -s [опция...] [аргумент ...]
В первом случае интерпретируется заданный командный файл (или содержимое стандартного ввода, если файл не указан), во втором - цепочка символов, в третьем команды читаются со стандартного ввода.
Большинство опций команд sh и set (см. выше) совпадают. Им может предшествовать не только знак минус, но и плюс, что означает инвертирование их смысла. Из специфических опций команды sh выделим -i, предписывающую считать shell интерактивным. Shell будет интерактивным и тогда, когда команды читаются со стандартного ввода, направленного, как и стандартный протокол, на терминал.
Заданные в командной строке аргументы становятся значениями фактических аргументов $1, $2 и т.д. Если при наличии опции -c задано имя_команды, то в результате интерпретации командной цепочки оно становится значением $0.
Пример. Команда
sh -c 'echo $0 $1 $2' a b c
выдаст на стандартный вывод
a b c
Читателю предлагается самостоятельно определить, что выдаст на стандартный вывод похожая команда
sh -c "echo $0 $1 $2" a b c
(вместо одиночных кавычек для экранирования пробелов использованы двойные), и объяснить полученный результат.
Командный интерпретатор можно вызвать и из программы на языке C, воспользовавшись функциями system() или popen() (см. листинг 2.30):
#include <stdlib.h>
int system (const char *command);
#include <stdio.h>
FILE *popen (const char *command,
const char *mode);
Аргумент command язык shell трактует как командную цепочку в вызове
sh -c command
и может содержать system() вызвавшая программа ожидает завершения выполнения переданной команды, а затем продолжает выполнение со следующего выполняемого оператора. Возвращаемое функцией system() значение - код завершения shell. Пример вызова функции system():
code = system
("cd /usr/bin; ./ls > /tmp/lst");
Функция popen(), как и system(), вызывает выполнение указанной команды. Отличие в том, что при использовании функции popen() создается канал между вызвавшей ее программой и командой. Аргумент mode определяет, что программа будет делать с этим каналом: читать (" r ") или писать (" w ").
К теме командного языка и его интерпретатора логически примыкают правила формирования и средства разбора командных строк.
В общем случае командная строка состоит из имени служебной программы (утилиты), опций,
Разработчики служебных программ должны руководствоваться следующими правилами.
Стандартным средством разбора опций и их аргументов, заданных в командной строке, является служебная программа getopts (чаще всего реализуемая как обычная встроенная команда shell):
getopts цепочка_имен_опций переменная
[аргумент ...]
Как правило, shell-процедура, запущенная разбираемой командной строкой, вызывает утилиту getopts многократно (в цикле). При каждом таком вызове getopts помещает имя очередной выделенной ею опции в качестве значения заданной переменной. Место продолжения разбора (индекс в командной строке) запоминается в shell-переменной OPTIND, начальным значением которой служит единица.
Если у опции должен быть аргумент (что обозначается двоеточием после имени опции в getopts выделяет его из командной строки и помещает в shell-переменную OPTARG.
Заданную при вызове getopts переменную, а также OPTIND и OPTARG следует использовать в качестве локальных, они не должны экспортироваться в окружение.
Если при вызове getopts указаны аргументы, разбираются они, а не элементы упомянутой выше командной строки.
Когда в командной строке обнаруживается опция, отсутствующая в цепочке имен опций, значением заданной переменной становится знак вопроса. Если первый символ цепочки имен опций - двоеточие, то в OPTARG помещается обнаруженный символ, иначе в стандартный протокол выдается диагностическое сообщение. С точки зрения shell-процедуры, вызвавшей getopts, это должно считаться ошибкой; с точки зрения getopts - нет.
По достижении конца опций утилита getopts возвращает положительный код завершения, а в OPTIND помещается номер первого элемента командной строки, не являющегося опцией.
Если переменой OPTIND присваивается единица, осуществляется (новый) разбор текущей командной строки (сформированной, быть может, с помощью set ) или текущих значений заданных при вызове getopts аргументов.
В качестве примера использования служебной программы getopts рассмотрим фрагмент shell-процедуры cmd, где производится разбор заданных при ее вызове опций (см. листинг 2.31).
while getopts :abo: c
do
case $c in
a | b) FLAG=$c;;
o) OARG=$OPTARG;;
?) printf "Использование: %s: [-a | -b] [-o выходной_файл] [аргумент ...]\n" $0
exit 1;;
esac
done
shift $(($OPTIND - 1))
printf "Заданный флаг: %s\n" $FLAG
printf "Аргумент опции o: %s\n" $OARG
printf "Остаток командной строки: %s\n" "$*"
Если вызвать shell-процедуру cmd любым из способов, показанных в листинге 2.32, будет выдан результат, приведенный в листинге 2.33.
cmd -a -o f1.o,f2.o file1 - cmd -ba -o f1.o,f2.o -- file1 - cmd -o f1.o,f2.o -b -a file1 -
Заданный флаг: a Аргумент опции o: f1.o,f2.o Остаток командной строки: file1 -
Для разбора опций и их аргументов средствами языка C служит функция getopt() и ассоциированные с ней внешние переменные (см. листинг 2.34).
#include <unistd.h>
int getopt (int argc, char *const argv[],
const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
В общем и целом логика ее работы та же, что и у служебной программы getopts (с соответствующим переименованием используемых переменных). Аргументы argc и argv задают командную строку в том виде, как она передается функции main(), optstring представляет собой цепочку имен опций. Переменная optind (с начальным значением 1 ) служит индексом в массиве argv []. Стандарт POSIX-2001 не специфицирует, как именно getopt() разбирает несколько расположенных поочередно (после одного знака минус) имен опций и определяет, какие опции уже обработаны.
В итоге функция getopt() возвращает имя очередной опции из числа перечисленных в цепочке optstring (если таковое удалось выделить). При наличии у опции аргумента указатель на него помещается в переменную optarg с соответствующим увеличением значения optind.
Если у опции аргумент отсутствует, а на первом месте в optstring задано двоеточие, оно и служит результатом.
Если встретилось optstring, или у опции нет аргумента, а на первом месте в optstring задано не двоеточие, то результатом станет знак вопроса.
В любой из перечисленных выше ошибочных ситуаций в переменную optopt помещается имя "проблемной" опции. Кроме того, в стандартный протокол выдается диагностическое сообщение по образу и подобию утилиты getopts. Для подавления выдачи следует присвоить переменной opterr нулевое значение.
Наконец, если при вызове getopt() указатель argv [optind] не отмечает начало опции (например, он пуст или первый символ указуемой цепочки отличен от знака минус), результат равен -1 как признак того, что разбор опций закончен.
Следующий пример программы (см. листинг 2.35) возвращает нас к рассмотренной выше shell-процедуре cmd. Он показывает, как можно обработать командную строку вызова утилиты, допускающей взаимоисключающие опции a и b, а также опцию o, которая должна иметь аргумент.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа разбирает опции вызвавшей ее командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
int main (int argc, char *argv []) {
int c; /* Имя анализируемой опции */
int aflg = 0; /* Признак того, что задана опция a */
int bflg = 0; /* Признак того, что задана опция b */
int errflg = 0; /* Флаг наличия ошибки в командной строке */
int flg = '?'; /* Флаг (a или b), заданный в командной строке */
char *ofile = NULL; /* Указатель на аргумент опции o */
/* Подавим стандартную диагностику */
/* независимо от первого символа */
/* цепочки имен опций */
opterr = 0;
while ((c = getopt (argc, argv, ":abo:")) != -1) {
switch (c) {
case 'a':
aflg++;
flg = c;
if (bflg) {
fprintf (stderr, "Опции a и b несовместимы\n");
errflg++;
}
break;
case 'b':
bflg++;
flg = c;
if (aflg) {
fprintf (stderr, "Опции a и b несовместимы\n");
errflg++;
}
break;
case 'o':
ofile = optarg;
break;
case ':':
fprintf (stderr, "Отсутствует аргумент опции -%c\n", optopt);
errflg++;
break;
case '?':
fprintf (stderr, "Недопустимая опция -%c\n", optopt);
errflg++;
break;
}
}
if (errflg) {
(void) fprintf (stderr, "Использование: %s: [-a | -b] [-o выходной_файл] "
"[аргумент ...]\n", argv [0]);
return (1);
}
printf ("Заданный флаг: %c\n", flg);
printf ("Аргумент опции o: %s\n", ofile);
printf ("Остаток командной строки:");
for (; optind < argc; optind++) {
printf (" %s", argv [optind]);
}
printf ("\n");
return 0;
}
Приведенная программа отличается от shell-процедуры cmd более тщательным анализом ошибочных ситуаций и детальной диагностикой.
В дальнейшем изложении слово shell будет употребляться в двух смыслах - как имя
Для нас shell важен прежде всего как мощный язык
Свойства shell как интерактивного командного интерпретатора, непосредственно взаимодействующего
с пользователем, хранение и использование истории сеанса, возможности редактирования
Выделим основные понятия языка shell на лексическом уровне.
#, а также вся последующая часть строки
На синтаксическом уровне различаются несколько видов команд.
Значение простой команды - ее код завершения.
|
;, , или || ; и имеют равные приоритеты, меньшие, чем у и ||. Приоритеты последних также равны между собой. Символ ; означает, что - параллельно (т. е. shell не ожидает завершения ( || ) означает, что список, следующий за ней, будет выполняться лишь в том случае, если код завершения предыдущего ; можно использовать символ перевода строки.
Командная строка - строка текста на языке shell.
Для выполнения (почти) каждой простой команды shell порождает отдельный процесс, в рамках которого выполняется программа, хранящаяся в файле, заданном именем команды. Программа может быть выполнимой, т. е. содержать машинные инструкции, или представлять собой shell-процедуру - содержать текст на языке shell.
Далее при описании синтаксиса конструкций языка shell и способа вызова служебных программ будут использоваться следующие соглашения:
Например, утилита ls не подсчитывает число файлов в каталоге, а лишь выдает информацию о них. С другой стороны, служебная программа wc способна подсчитать число строк в файле, но не имеет отношения к распечатке содержимого каталогов. Если же построить wc, вызванная без аргументов, обрабатывает файл стандартного ввода, который в данном случае является результатом работы команды ls ). Значит, в каталоге /bin 91 файл, если считать и элементы, соответствующие текущему и вышележащему каталогам (первая строка выдачи ls содержит суммарное число блоков, занятых файлами каталога).
ls -al /bin | wc -l
Еще один пример. Пусть необходима информация о файлах текущего каталога, модифицированных в октябре. К цели ведет
ls -al | grep "Oct "
Служебная программа grep играет здесь роль ls.
Можно выстроить и трехступенчатый
ls -al | grep "Oct " | wc -l
Здесь утилиту grep с еще большим правом можно назвать фильтром.
Приведем еще один пример
ls -Rl /dev | more
tee файл ...
Например, если нужно не только подсчитать число файлов из текущего каталога, модифицированных в октябре, но и поместить информацию о них в файл для последующего анализа, следует построить четырехступенчатый конвейер (см. листинг 2.5):
ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l
В результате его выполнения на экране появится сообщение о количестве нужных файлов, а в файле /tmp/tmpinf - информация о них.
Переменные обозначаются именами. Значения могут присваиваться им привычным способом, то есть посредством команд вида:
имя=значение [имя=значение] ...
Все значения в языке shell трактуются как текстовые. Подчеркнем, что, в соответствии с этими требованиями, конструкция
имя=значение
должна представлять собой одно слово - в ней не может быть пробелов.
Обычно в языках программирования ясно из контекста, где подразумевается имя переменной, а где значение. Так, в левой части оператора присваивания обычно используется имя, в правой - значение. В shell все не так. Переход от имени переменной к значению помечается посредством явной операции $. Если в команде встречается конструкция
$имя
то вместо нее интерпретатор shell подставляет значение переменной с указанным именем. Допускается и запись
${имя}
с тем же смыслом, если нужно отделить имя от последующего текста.
Рассмотрим пример. После выполнения команд (см. листинг 2.6) - утилита echo (эхо) выдает на стандартный вывод свои аргументы - на экране появится результат вывода значений переменных (см. листинг 2.7).
a=value_of_variable b=1+2 echo a = $a echo b = $b
a = value_of_variable b = 1+2
Значения формальных аргументов shell-процедур обозначаются как
$цифра
$0 - это имя интерпретируемой shell-процедуры. Если заданных при вызове команды фактических аргументов меньше, чем 9, "лишние" формальные аргументы получают пустые значения. О том, как добраться до фактических аргументов с номерами большими, чем 9, будет сказано ниже (см. управляющую конструкцию for и команду shift ).
В качестве примера рассмотрим shell-процедуру, которая выдает на стандартный вывод свое имя и значения трех первых аргументов (см. листинг 2.8).
echo Имя команды: $0 echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Пусть приведенный текст помещен в файл с именем three_args. Тогда после выполнения команды (см. листинг 2.9) на экране появится ее результат (см. листинг 2.10).
three_args arg1 . - arg4
Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Поскольку в тексте shell-процедуры упомянуты только первые 3 аргумента, значения аргументов с большими номерами (даже если они заданы, как в приведенном примере) не влияют на ее работу.
Команда
three_args arg1 --
выдаст другой результат (см. листинг 2.11):
Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: -- Значение третьего аргумента:
Значение третьего формального аргумента пусто.
Прежде чем начнется выполнение командной строки, т. е. будет вызвана заданная в ней команда, строку обрабатывает shell, выполняющий в ней некоторые подстановки. С одной из них - значения переменной вместо конструкции $имя - мы уже познакомились. Язык shell содержит ряд аналогичных конструкций. Рассмотрим их.
Там, где в командной строке встречается запись вида
${имя:-слово}
вместо нее подставляется значение переменной с указанным именем, если это значение непусто, в противном случае подставляется слово. Например, при наличии в shell-процедуре присваивания
initdir=${1:-/}
переменная initdir получит значение первого аргумента, если оно непусто. Если же процедуру вызвали без аргументов, значением initdir станет символ /. Подобные конструкции - удобный способ использования подразумеваемых значений.
Запись вида
${имя:=слово}
влечет присваивание нового значения (слово) переменной с заданным именем, только если ее старое значение не установлено или пусто (удобный способ присваивания переменным подразумеваемых значений). Вместо всей конструкции в командную строку подставляется итоговое (непустое старое или присвоенное новое) значение переменной.
Конструкция
${имя:?слово}
предназначена для выдачи в стандартный протокол сообщения об ошибке, если значение переменной с заданным именем не установлено или пусто, после чего неинтерактивный shell завершает работу с ненулевым кодом возврата. Непустое значение подставляется в командную строку, и интерпретация командного файла продолжается обычным образом.
Вместо конструкции
${имя:+слово}
подставляется слово, если значение переменной с заданным именем непусто; в противном случае не подставляется ничего. Такой способ удобен для использования альтернативных значений.
Если в приведенных выше конструкциях опустить двоеточие, будет отменена проверка непустоты значения (останется лишь проверка того, установлено ли значение переменной с заданным именем. В остальном смысл конструкций остается прежним.
Shell содержит базовые средства для обработки цепочек символов. Так, вместо конструкции
${#имя}
подставляется число символов в значении переменной с заданным именем.
Предоставляется четыре конструкции для
${имя%слово}
${имя%%слово}
${имя#слово}
${имя##слово}
Во всех случаях слово рассматривается как образец (
Приведем несколько примеров. Для их понимания достаточно знать, что шаблон * сопоставляется с произвольной цепочкой символов, в том числе с пустой.
После присваивания переменной
HOME=/home/galat
команда
echo ${#HOME}
выдаст 11.
Заменить в имени файла суффикс .f на .for можно с помощью команд, показанных в листинге 2.12:
f=file.f
mv $f ${f%.f}.for
Выдать первый элемент маршрутного имени файла (см. далее) можно с помощью команд, показанных в листинге 2.13:
f=маршрутное_имя
echo ${f%%/*}
Последний элемент выдается командой, приведенной в листинге 2.14:
echo ${f##*/}
Значения некоторых переменных устанавливаются самим языком shell. Перечислим эти переменные и опишем их предназначение.
#
Количество фактических аргументов (десятичное).
-
Флаги (однобуквенные set (см. далее).
?
Десятичное значение, возвращенное предыдущей
$
!
Идентификатор последнего
*, @
Совокупность всех фактических аргументов (начиная с $1 ), разделенных пробелами.
Напомним: чтобы получить значения этих переменных, перед ними нужно поставить знак $.
Значения $@ и $* имеют некоторые тонкие различия, на которых мы, однако, останавливаться не будем.
Несколько усложним процедуру three_args, чтобы продемонстрировать только что описанные возможности (см. листинг 2.15).
echo Идентификатор текущего процесса: $$ echo Имя команды: $0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Теперь, вызывая усовершенствованную процедуру three_args, выполним командную строку
three_args arg1 . - arg4
Тогда на экране появится примерно следующее (см. листинг 2.16):
Идентификатор текущего процесса: 3882 Имя команды: three_args Число фактических аргументов: 4 Совокупность всех аргументов: arg1 . - arg4 Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Окружение - это набор пар (имя, значение), который передается выполняемой программе так же, как и обычный список аргументов. Иными словами, порождаемые процессы наследуют
HOME
Подразумеваемый аргумент утилиты cd -
IFS
Цепочка символов, где перечислены
PATH
PS1
Основное приглашение $ ").
TERM
Тип пользовательского терминала.
TZ
Информация о часовом поясе.
Для отсылки информации об окружении на стандартный вывод следует воспользоваться командой
env
Поясним подробнее смысл некоторых переменных окружения.
Прежде чем выполнить команду, shell ищет файл с соответствующим именем в последовательности каталогов, являющейся значением переменной PATH. Назовем подобную последовательность списком поиска. Если, например, значение $PATH суть
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
то нужный файл будет сначала разыскиваться в каталоге /usr/local/bin, затем в /bin и т.д. Как только файл отыщется, поиск прекратится. Это важно, если в разных каталогах есть одноименные выполнимые файлы.
Значение $IFS влияет не только на разбор команд, но и на чтение строк данных с помощью команды read (см. далее). Кроме того, первый символ из значения $IFS вставляется между фактическими аргументами при выполнении подстановки $*.
Переменная TERM хранит тип терминала пользователя. Интерактивные утилиты (редакторы или другие программы с talk ) с помощью значения $TERM настраиваются на конкретный тип терминала.
Переменная TZ задает локальный часовой пояс. Эта информация необходима всегда, когда требуется показать текущее время. Особенно полезна она при
Для изменения окружения мало присвоить новое значение соответствующей переменной. Дело в том, что по умолчанию переменные считаются локальными по отношению к shell-процедуре, т. е. присваивание изменит локальную переменную, но не затронет одноименную переменную окружения. Таким образом, в окружение новых процессов (порожденных, например, для выполнения последующих команд данной shell-процедуры) войдет переменная со старым значением.
С помощью конструкции
export имя[=слово]
переменная с указанным именем и значением (последнее может быть опущено) помещается в окружение, т. е. становится глобальной.
Следующая строка иллюстрирует типичное применение команды export:
export PATH=/local/bin:$PATH
Переменная PATH изменилась, и новое значение экспортировано в окружение.
Команда
export -p
выдает на стандартный вывод имена и значения всех экспортированных переменных. Эту выдачу можно использовать для сохранения и последующего восстановления (быть может, с некоторыми модификациями) окружения. Ниже приведен фрагмент возможного результата работы команды export -p (см. листинг
2.17).
export HISTSIZE="1000" export HOME="/home/galat" export LANG="C" export LESSCHARSET="koi8-r" export LOGNAME="galat" export MAIL="/var/spool/mail/galat" export TTY="/dev/ttyS4" export USER="galat"
Мы видим, что выдача устроена так, чтобы вновь быть поданной на вход командного интерпретатора.
Если в командной строке встретилась цепочка символов, заключенная в обратные кавычки ( ` ), она интерпретируется как команда, стандартный вывод которой подставляется вместо упомянутой конструкции. Говорят, что в этом случае производится
Эквивалентной синтаксической формой для подстановки результата выполнения команды является конструкция вида
$(команда)
Для выполнения заданной команды порождается процесс, в рамках которого работает еще один экземпляр командного интерпретатора.
Опишем несколько употребительных способов использования filelist содержит список имен других файлов и над совокупностью последних требуется проделать
некоторое действие. Если в командную строку поместить конструкцию
... `cat filelist` ...
то это аналогично явному указанию в командной строке на все файлы из списка. (Утилита cat выдает на стандартный вывод содержимое файлов, заданных в качестве аргументов.) Пример
ls -l `cat filelist`
Нет нужды в том, чтобы каждая служебная программа умела читать свои аргументы из файла: имеются универсальный механизм cat.
${x:-$(ls)}
команда ls выполнится только тогда, когда значение x не установлено или пусто; в противном случае выполнится команда $x, и ее результат займет место в командной строке.
В языке shell все значения считаются текстовыми. Значит, для выполнения операций с числами нужны особые средства. Вместо конструкции вида
$((выражение))
shell подставит результат
i=$(($i+1))
значение i (если оно было числом) увеличится на единицу.
Стандарт POSIX обязывает поддерживать арифметику длинных целых со знаком; константы (десятичные, восьмеричные, шестнадцатеричные) должны удовлетворять требованиям языка C.
Среди прочих язык shell содержит следующие управляющие конструкции.
Оператор for:
for имя [in слово ...] do список done
При каждой итерации переменная имя принимает следующее значение из набора
in слово ...
Если конструкция in слово ... опущена, то список выполняется для каждого формального аргумента.
Условный оператор:
if список_1 then список_2 [elif список_3 then список_4] ... [else список_5] fi
Выполняется список_1. Если код его завершения 0, то выполняется список_2, в противном случае - список_3, и если код его завершения 0, то выполняется список_4 и т.д. Если же коды завершения всех списков, использованных в качестве условий, оказались ненулевыми, выполняется else -часть ( список_5 ). Если else -часть отсутствует и ни одна then -часть не выполнялась, возвращается нулевой код завершения.
Оператор цикла while ( until ):
while список_1 do список_2 done
Пока код завершения последней команды списка_1 есть 0, выполняются команды списка_2. При замене служебного слова while на until списка_2 не выполнялись вообще, код завершения устанавливается равным нулю.
Оператор выбора:
case слово in [шаблон [| шаблон] ...) список ;;] ... esac
Выполняется список, соответствующий первому из шаблонов, успешно сопоставленных со словом. Формат шаблона аналогичен используемому для генерации имен файлов (см. далее).
имя () открывающая_скобка список закрывающая_скобка
Определяется
имя [аргумент ...]
Если тело функции заключено в фигурные скобки, она выполняется в рамках текущего процесса; в случае круглых скобок порождается отдельный процесс. На время выполнения функции $1, $2, ..., а также служебная переменная # получают новые значения, определенные аргументами команды вызова. Затем восстанавливаются старые значения.
Приведем примеры использования управляющих конструкций. Сначала усовершенствуем процедуру three_args, чтобы она выдавала значения всех, а не только первых трех аргументов (см. листинг 2.18):
echo Идентификатор текущего процесса: $$
echo Имя команды: $0
echo Число фактических аргументов: $#
echo Совокупность всех аргументов: $@
i=1
for arg
do
echo Значение аргумента номер ${i}: $arg
i=$(($i+1))
done
В управляющих конструкциях if и while часто используется команда test, она проверяет некоторое условие и вырабатывает нулевой или ненулевой код завершения в зависимости от того, оказалось ли условие, соответственно, истинным или ложным. Команда записывается любым из двух способов:
test условие
или употребив пару квадратных скобок:
[ условие ]
Условие может включать, помимо прочих, следующие примитивы:
-r файл
Истина, если файл существует и доступен для чтения.
-w файл
Истина, если файл существует и доступен для записи.
-x файл
Истина, если файл существует и является выполнимым.
-f файл
Истина, если файл существует и является обычным файлом.
-d файл
Истина, если файл существует и является каталогом.
-c файл
Истина, если файл существует и является специальным символьным файлом.
-b файл
Истина, если файл существует и является специальным блочным файлом.
-p файл
Истина, если файл существует и является именованным каналом.
-s файл
Истина, если файл существует и имеет ненулевой размер.
-n s1
Истина, если цепочка символов s1 имеет ненулевую длину.
s1 = s2
Истина, если цепочки символов s1 и s2 равны.
s1 != s2
Истина, если цепочки символов s1 и s2 не равны.
n1 -eq n2
Истина, если целые числа n1 и n2 алгебраически равны. На месте -eq могут быть также операции сравнения -ne (не равно), -gt (больше), -ge (больше или равно), -lt (меньше), -le (меньше или равно).
Примитивы комбинируются с помощью круглых скобок, а также следующих операций (в порядке уменьшения приоритета):
| ! | Унарная |
-a |
|
-o |
Обратим внимание на то, что квадратные скобки, обрамляющие условие, и каждый компонент условия должны быть выделены пробелами.
Приведем пример использования управляющей конструкции if. В процессе загрузки практически любой разновидности ОС Unix выполняются строки следующего или близкого вида (см. листинг 2.19):
if [ -s ${f} ]
then
/bin/sh ${f} start
fi
Если файл, имя которого является значением переменной f, существует и имеет ненулевой размер, он выполняется с аргументом start.
В качестве примера употребления конструкции case приведем еще один фрагмент, типичный для процесса загрузки системы (см. листинг 2.20):
case "$1" in
start)
start
;;
stop)
stop
;;
reload | restart)
restart
;;
condrestart)
if [ -f /var/lock/subsys/atd ]
then
restart
fi
;;
*)
echo $"Usage: $0 {start | stop | restart | condrestart}"
exit 1
esac
Известные значения первого аргумента распознаются, в ответ на все прочие (шаблон *) сообщается, как пользоваться данной shell-процедурой.
Следующий пример иллюстрирует определение и вызов функции (см. листинг 2.21).
echo $# $1
f ( ) {
echo $# $1
}
f a b
f b
echo $# $1
Если данный текст хранится в файле g, то по окончании выполнения команды (вызов shell-процедуры, содержащей функцию)
g c d e
будет выдан следующий результат:
3 c 2 a 1 b 3 c
После всех подстановок, прежде чем команда начнет выполняться, в каждом составляющем ее поле осуществляется поиск символов ?, и . в начале имени файла или непосредственно после ? и [:
* - сопоставляется с произвольной цепочкой символов, в том числе с пустой. |
? - сопоставляется с произвольным символом. | !, шаблону удовлетворяет любой символ, не перечисленный в скобках. |
Рассмотрим несколько примеров. Чтобы подсчитать суммарное число строк во всех C -файлах текущего каталога, достаточно выполнить команду
wc -l *.c
В качестве второго примера расширим приведенный выше фрагмент действий при загрузке системы (см. листинг 2.22):
for f in /etc/rc$runlevel.d/S*
do
if [ -s ${f} ]
then
/bin/sh ${f} start
fi
done
В цикле будут в алфавитном порядке запускаться все непустые файлы из каталога, соответствующего заданному уровню выполнения (обычно это /etc/rc3.d, поскольку имеет значение 3, но в данном случае это не суть важно), имена которых начинаются на S.
Как пример использования шаблона имен файлов приведем команду
rm -f .*.[Bb]?
Она удалит из текущего каталога все файлы, имена которых начинаются с точки, сразу после второй точки стоит одна из букв - B или b, а затем произвольный символ.
Читателю предлагается рассмотреть еще один способ применения шаблона имен файлов и самостоятельно ответить на вопрос, удалит ли команда
rm -f *.[Bb][AaUu]
файл с именем .bu.
Наконец, рассмотрим более сложный пример. Пусть требуется так переименовать фортрановские файлы текущего каталога, чтобы окончание .f заменилось на .for. Служебная программа
basename цепочка_символов [суффикс]
убирает из цепочки_символов любой оканчивающийся на / префикс и суффикс (если он есть) и выдает результат на стандартный вывод. Применим ее для решения сформулированной задачи (см. листинг 2.23).
for f in *.f do mv $f `basename $f .f`.for done
Особенностью приведенного примера является использование
Еще одним видом генерации маршрутных имен файлов можно было бы считать обработку символа ~ ', однако это действие открывает, а не завершает подстановки в командной строке. Префикс слов, от тильды до ближайшего слэша (или конца слова, если слэш отсутствует), заменяется на имя домашнего каталога пользователя, HOME. В присваиваниях тильда распознается и обрабатывается не только в начале слова, но и после знаков равенства и двоеточия, что позволяет, в частности, естественным образом обращаться со значением переменной окружения PATH.
Так, при подстановке результатом работы команды echo ~ может быть /home/galat.
Командный интерпретатор shell ассоциирует с каждым открытым файлом так называемый дескриптор. Дескрипторы нумеруются десятичными целыми числами, начиная с нуля. Верхняя граница зависит от реализации, но, согласно стандарту POSIX, должны поддерживаться по крайней мере десять одновременно открытых файлов (с
Дескрипторы с номерами 0, 1 и 2 имеют специальный смысл. Они соответствуют стандартному вводу, стандартному выводу и стандартному протоколу.
Перед выполнением команды ее ввод и вывод могут быть перенаправлены, для чего используется специальная нотация, которую интерпретирует shell. Описанные ниже конструкции располагаются в любом месте простой команды, предшествуют команде или завершают ее и не передаются в качестве аргументов команды; если они завершают определение функции, то воздействуют на все команды тела, не производящие собственных перенаправлений.
Перенаправление ввода:
<слово
Использовать файл слово для стандартного ввода (
Перенаправление вывода с перезаписью:
>слово >|слово
Использовать файл слово для стандартного вывода (дескриптор файла 1). Имеющийся файл опустошается, а если файла нет, он создается. (При использовании первой разновидности -C команды set.)
Перенаправление вывода с дозаписью:
>>слово
Использовать файл слово для стандартного вывода. Если файл существует, то выводимая информация добавляется в конец (сначала производится поиск конца файла); в противном случае файл создается. Если любой из этих конструкций предшествует цифра, она определяет дескриптор (вместо подразумеваемых дескрипторов 0 или 1), который будет ассоциирован с файлом, указанным в конструкции. Например, строка
... 2>protocol
перенаправляет стандартный протокол (дескриптор 2) в файл по имени protocol.
Ввод и вывод могут перенаправляться не только в файл с заданным именем, но и в открытый файл с заданным дескриптором. Для этого в описанных выше конструкциях в качестве слова следует употребить связку номер_дескриптора:
<номер_дескриптора >номер_дескриптора
Например, чтобы перенаправить стандартный протокол туда же, куда назначен стандартный вывод, употребить конструкцию
... 2>1
Если в качестве номера_дескриптора указан минус (' - '), соответствующий файл (стандартный ввод, вывод или явно заданный своим дескриптором файл) закрывается.
Shell позволяет открыть файл одновременно на чтение и запись при помощи конструкции
<>слово
Ей, как всегда, может предшествовать номер дескриптора для открываемого файла. По умолчанию используется 0, то есть стандартный ввод.
Приведем два примера. Пусть нужно измерить время выполнения некоторой команды, направив ее результаты в файл cmd.res, а данные о времени - в файл cmd.time. К цели ведет строка, приведенная в листинге 2.24:
time команда >cmd.res 2>cmd.time
Второй пример. Рассмотрим цикл, описанный в листинге 2.25:
i=0 while [ $i -lt 40 ] do > lost+found/g$i i=$(($i+1)) done rm lost+found/g*
С его помощью создается 40 файлов в каталоге lost+found, которые затем удаляются. Отметим, что перенаправляется стандартный вывод пустой команды, а в результате создается пустой файл.
(Поясним смысл приведенного фрагмента. При проверке и коррекции файловой системы утилитой fsck в каталог /lost+found помещаются непустые файлы, на которые нет ссылок. Сложность в том, что пока утилита fsck работает, ни один файл не должен расширяться, т. е. в каталоге /lost+found должны быть заранее заготовленные пустые места.)
Shell дает возможность размещать исходные данные в виде
<<слово вставка слово
и
<<-слово вставка слово
Их можно считать разновидностями перенаправления ввода. На вход команды подается вставка, представляющая собой череду строк вплоть до слова, указанного при перенаправлении. Второй вариант отличается тем, что из строк вставки удаляются начальные символы табуляции.
В качестве примера использования вставок приведем файл ОС Linux /etc/tripwire/twinstall.sh, который содержит следующие фрагменты (см. листинг 2.26 и листинг 2.27):
cat << END_OF_TEXT A clear-text version of the Tripwire configuration file $TXT_CFG has been preserved for your inspection. It is recommended that you delete this file manually after you have examined it. END_OF_TEXT
cat <<eof1; cat <<eof2 Вставка 1 eof1 Вставка 2 eof2
В результате выполнения второго фрагмента будут выданы две строки:
Вставка 1 Вставка 2
| ; < > ( ) $ ` \ " ' пробел перевод_строки
Не являясь разделителями слов, особую роль играют для shell и символы
* ? [ ] # ~ = %
Каждый из перечисленных выше символов может быть экранирован, т. е. представлять самого себя, если перед ним стоит \ либо он расположен между одиночными или двойными кавычками ( '' или "" ).
Экранирование с помощью двойных кавычек имеет некоторые особенности. Во-первых, оно не отменяет особой трактовки символов $ и `. Иными словами, внутри двойных кавычек обычным образом выполняется подстановка значений переменных, результатов выполнения команд и результатов вычисления арифметических выражений, только результат подстановки расценивается как одно слово. Во-вторых, особая трактовка символа \ отменяется внутри двойных кавычек лишь частично: символ \ сохраняет экранирующую роль, если после него следуют:
$ ` " \ перевод_строки
Приведем несколько примеров использования двойных кавычек для экранирования. В первом команда
echo "$(echo *)"
выдаст список файлов текущего каталога, а во втором результатом выполнения строки
echo "$(echo "*")"
станет звездочка. Если же аргумент " $(echo *) " предложить усовершенствованной процедуре three_args, то число фактических аргументов окажется равным единице, а список файлов текущего каталога станет значением первого аргумента.
Рассмотрим третий пример. Пусть нужно выдать информацию о всех файлах текущего каталога, которые модифицировались последний раз 3 октября.
ls -al | grep "Oct 3"
сделает то, что нужно (служебная программа grep выполняет сопоставление заданного шаблона со строками заданных файлов или стандартного ввода). Кавычки использованы для экранирования двух пробелов в разыскиваемой цепочке символов. Без экранирования пробелы превращаются в разделители аргументов, и команда grep попыталась бы найти текст Oct в файле с цифровым именем 3.
Еще один пример. Запись
$(($i\*5))
синтаксически неверна, поскольку в арифметических выражениях shell рассматривает звездочку как операцию умножения, а не как элемент шаблона имен файлов; соответственно, она не нуждается в экранировании, и символ \ становится лишним, нарушая синтаксис выражения. Таким образом, лишнее экранирование может быть вредным.
Мы уже отмечали, что подавляющее большинство команд разыскивается в списке поиска и выполняется языком shell в рамках порожденных процессов. Однако для некоторых команд - либо по соображениям эффективности, либо потому, что требуемую функциональность трудно достичь в рамках отдельного процесса - целесообразно сделать исключение и реализовать их как встроенные, их выполняет сам shell. Очевидным примером export - пометить переменную как глобальную, входящую в
Встроенные команды подразделяются на обычные и специальные. С точки зрения приложений первые ничем (кроме, быть может, эффективности обработки) не отличаются от невстроенных утилит. В частности, они могут фигурировать в качестве программы процесса. Специальные встроенные команды этим свойством обладать не обязаны. Кроме того, у них есть еще два отличия:
Перечислим некоторые из
. файл
Shell читает и выполняет команды из файла, затем возобновляется чтение со стандартного ввода; при поиске файла используется значение переменной . позволяет выполнить любую команду в рамках текущего процесса.
eval [аргумент ...]
Выполнить команду, заданную аргументами eval.
exec [аргумент ...]
exec, заменяет shell. В качестве аргументов могут указываться спецификации ввода/вывода, и, если нет никаких других аргументов, будет лишь перенаправлен ввод/вывод текущего процесса shell.
exit [код_завершения]
Завершить выполнение shell с указанным кодом. При отсутствии аргумента код завершения определяется последней выполненной командой. Чтение символа конца файла также приводит к завершению shell.
shift [n]
Формальные аргументы, начиная с ( n+1 )-го, переименовываются в $1 и т.д. По умолчанию n=1.
К числу
cd [каталог]
Сделать текущим заданный каталог. Если каталог не указан, используется значение переменной окружения HOME.
pwd
Выводит имя текущего каталога.
read [переменная ...]
Со стандартного ввода читается одна строка и делится на поля; первое поле присваивается первой переменной, второе - второй и т.д., причем последовательность всех оставшихся полей присваивается последней переменной. Исходная строка имеет продолжение, если в конце ее стоит последовательность \перевод_строки. Символы, отличные от перевода строки, также могут быть экранированы с помощью \, который удаляется перед присваиванием полей. Возвращается нулевой код завершения, если только не встретился конец файла.
Некоторые пояснения. С помощью команды exec осуществляется смена программы текущего процесса. Например, если пользователь привык работать в командном интерпретаторе bash, он может перейти туда по команде
exec bash
При этом обычный shell бесследно исчезнет.
Аргументы eval обрабатываются дважды: когда исходную строку интерпретирует shell и когда работает сама eval. Так, после выполнения строк
a=1 b=a eval c='$'$b echo $c
на стандартный вывод будет выдана единица, поскольку аргументом eval будет оператор присваивания c=$a.
Если файл data содержит строку
1 2 3 4 5
то после выполнения read
read a b c echo $c $b $a
на стандартный вывод будет выдан следующий результат:
3 4 5 2 1
Текст " 3 4 5 " (остаток строки) стал значением переменной c.
Команду shift используют при последовательной обработке аргументов shell-процедур. Например, если требуется выдать аргументы, которые являются именами существующих обычных файлов, употребляют цикл вида (см. листинг 2.28):
while [ "$1" ] do [ -f $1 ] echo $1 shift done
Важную роль играет еще одна специальная встроенная команда - set. Она используется для изменения режима работы shell и для присваивания новых значений формальным аргументам. Сразу же отметим, что наивное присваивание
цифра=слово
shell трактует как запуск программы с именем цифра=слово, т. е. оно не позволяет изменить значение формального аргумента.
Команда set записывается в виде
set [опция ...] [аргумент ...]
Из ее опций упомянем следующие:
-a
-C
Защищать существующие файлы от перезаписи при > " (см. выше раздел "Перенаправление ввода/вывода").
+o
Выдать на стандартный вывод текущие опции языка shell в виде команд, которые устанавливают те же значения и которые можно подать ему на вход.
-x
Устанавливает режим +x выключает трассировку. Стандарт не специфицирует, будет ли трассироваться команда set +x.
Если в команде set заданы аргументы, они станут новыми значениями $1, $2 и т.д. Типичный пример использования команды set содержится в одном из файлов ОС Linux ( /etc/rc.d/rc ) выполняемых при загрузке системы (см. листинг 2.29):
argv1="$1" set `/sbin/runlevel` runlevel=$2 previous=$1
Возможностью, не обязательной с точки зрения стандарта POSIX-2001 (отнесенной к расширению "Мобильность пользователей", UP), но весьма полезной на практике, является механизм alias:
alias [имя[=синоним] ...]
Когда shell выделяет в командной строке слово, расположенное вместо имени простой команды, он проверяет, не является ли оно именем синонима, и при положительном ответе заменяет его значением, заданным командой alias и представляющим собой цепочку символов.
Например, если хочется, чтобы команда ls по умолчанию выдавала информацию о файлах, имена которых начинаются с точки, можно воспользоваться спецификацией для определения синонима:
alias ls="ls -a"
Если подставленный синоним оканчивается пробелом, то анализируется следующее слово командной строки; если и оно оказывается именем синонима, подстановки продолжаются. Например, если синоним для ls задать в виде строки с завершающим пробелом
alias ls="ls -a "
и дополнительно ввести еще одно определение синонима
alias l="-Rl"
то команда, использующая синоним с завершающим пробелом,
ls l
будет раскрыта в строку
ls -a -Rl
и выдаст в длинном формате информацию о всех файлах текущего каталога и его подкаталогов.
Команда alias без аргументов выдает список имеющихся синонимов.
Вызов командного интерпретатора shell осуществляется командой
sh [опция...] [командный_файл [аргумент ...]]
или
sh -c [опция...] командная_цепочка
[имя_команды [аргумент ...]]
или
sh -s [опция...] [аргумент ...]
В первом случае интерпретируется заданный командный файл (или содержимое стандартного ввода, если файл не указан), во втором - цепочка символов, в третьем команды читаются со стандартного ввода.
Большинство опций команд sh и set (см. выше) совпадают. Им может предшествовать не только знак минус, но и плюс, что означает инвертирование их смысла. Из специфических опций команды sh выделим -i, предписывающую считать shell интерактивным. Shell будет интерактивным и тогда, когда команды читаются со стандартного ввода, направленного, как и стандартный протокол, на терминал.
Заданные в командной строке аргументы становятся значениями фактических аргументов $1, $2 и т.д. Если при наличии опции -c задано имя_команды, то в результате интерпретации командной цепочки оно становится значением $0.
Пример. Команда
sh -c 'echo $0 $1 $2' a b c
выдаст на стандартный вывод
a b c
Читателю предлагается самостоятельно определить, что выдаст на стандартный вывод похожая команда
sh -c "echo $0 $1 $2" a b c
(вместо одиночных кавычек для экранирования пробелов использованы двойные), и объяснить полученный результат.
Командный интерпретатор можно вызвать и из программы на языке C, воспользовавшись функциями system() или popen() (см. листинг 2.30):
#include <stdlib.h>
int system (const char *command);
#include <stdio.h>
FILE *popen (const char *command,
const char *mode);
Аргумент command язык shell трактует как командную цепочку в вызове
sh -c command
и может содержать system() вызвавшая программа ожидает завершения выполнения переданной команды, а затем продолжает выполнение со следующего выполняемого оператора. Возвращаемое функцией system() значение - код завершения shell. Пример вызова функции system():
code = system
("cd /usr/bin; ./ls > /tmp/lst");
Функция popen(), как и system(), вызывает выполнение указанной команды. Отличие в том, что при использовании функции popen() создается канал между вызвавшей ее программой и командой. Аргумент mode определяет, что программа будет делать с этим каналом: читать (" r ") или писать (" w ").
К теме командного языка и его интерпретатора логически примыкают правила формирования и средства разбора командных строк.
В общем случае командная строка состоит из имени служебной программы (утилиты), опций,
Разработчики служебных программ должны руководствоваться следующими правилами.
Стандартным средством разбора опций и их аргументов, заданных в командной строке, является служебная программа getopts (чаще всего реализуемая как обычная встроенная команда shell):
getopts цепочка_имен_опций переменная
[аргумент ...]
Как правило, shell-процедура, запущенная разбираемой командной строкой, вызывает утилиту getopts многократно (в цикле). При каждом таком вызове getopts помещает имя очередной выделенной ею опции в качестве значения заданной переменной. Место продолжения разбора (индекс в командной строке) запоминается в shell-переменной OPTIND, начальным значением которой служит единица.
Если у опции должен быть аргумент (что обозначается двоеточием после имени опции в getopts выделяет его из командной строки и помещает в shell-переменную OPTARG.
Заданную при вызове getopts переменную, а также OPTIND и OPTARG следует использовать в качестве локальных, они не должны экспортироваться в окружение.
Если при вызове getopts указаны аргументы, разбираются они, а не элементы упомянутой выше командной строки.
Когда в командной строке обнаруживается опция, отсутствующая в цепочке имен опций, значением заданной переменной становится знак вопроса. Если первый символ цепочки имен опций - двоеточие, то в OPTARG помещается обнаруженный символ, иначе в стандартный протокол выдается диагностическое сообщение. С точки зрения shell-процедуры, вызвавшей getopts, это должно считаться ошибкой; с точки зрения getopts - нет.
По достижении конца опций утилита getopts возвращает положительный код завершения, а в OPTIND помещается номер первого элемента командной строки, не являющегося опцией.
Если переменой OPTIND присваивается единица, осуществляется (новый) разбор текущей командной строки (сформированной, быть может, с помощью set ) или текущих значений заданных при вызове getopts аргументов.
В качестве примера использования служебной программы getopts рассмотрим фрагмент shell-процедуры cmd, где производится разбор заданных при ее вызове опций (см. листинг 2.31).
while getopts :abo: c
do
case $c in
a | b) FLAG=$c;;
o) OARG=$OPTARG;;
?) printf "Использование: %s: [-a | -b] [-o выходной_файл] [аргумент ...]\n" $0
exit 1;;
esac
done
shift $(($OPTIND - 1))
printf "Заданный флаг: %s\n" $FLAG
printf "Аргумент опции o: %s\n" $OARG
printf "Остаток командной строки: %s\n" "$*"
Если вызвать shell-процедуру cmd любым из способов, показанных в листинге 2.32, будет выдан результат, приведенный в листинге 2.33.
cmd -a -o f1.o,f2.o file1 - cmd -ba -o f1.o,f2.o -- file1 - cmd -o f1.o,f2.o -b -a file1 -
Заданный флаг: a Аргумент опции o: f1.o,f2.o Остаток командной строки: file1 -
Для разбора опций и их аргументов средствами языка C служит функция getopt() и ассоциированные с ней внешние переменные (см. листинг 2.34).
#include <unistd.h>
int getopt (int argc, char *const argv[],
const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
В общем и целом логика ее работы та же, что и у служебной программы getopts (с соответствующим переименованием используемых переменных). Аргументы argc и argv задают командную строку в том виде, как она передается функции main(), optstring представляет собой цепочку имен опций. Переменная optind (с начальным значением 1 ) служит индексом в массиве argv []. Стандарт POSIX-2001 не специфицирует, как именно getopt() разбирает несколько расположенных поочередно (после одного знака минус) имен опций и определяет, какие опции уже обработаны.
В итоге функция getopt() возвращает имя очередной опции из числа перечисленных в цепочке optstring (если таковое удалось выделить). При наличии у опции аргумента указатель на него помещается в переменную optarg с соответствующим увеличением значения optind.
Если у опции аргумент отсутствует, а на первом месте в optstring задано двоеточие, оно и служит результатом.
Если встретилось optstring, или у опции нет аргумента, а на первом месте в optstring задано не двоеточие, то результатом станет знак вопроса.
В любой из перечисленных выше ошибочных ситуаций в переменную optopt помещается имя "проблемной" опции. Кроме того, в стандартный протокол выдается диагностическое сообщение по образу и подобию утилиты getopts. Для подавления выдачи следует присвоить переменной opterr нулевое значение.
Наконец, если при вызове getopt() указатель argv [optind] не отмечает начало опции (например, он пуст или первый символ указуемой цепочки отличен от знака минус), результат равен -1 как признак того, что разбор опций закончен.
Следующий пример программы (см. листинг 2.35) возвращает нас к рассмотренной выше shell-процедуре cmd. Он показывает, как можно обработать командную строку вызова утилиты, допускающей взаимоисключающие опции a и b, а также опцию o, которая должна иметь аргумент.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа разбирает опции вызвавшей ее командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
int main (int argc, char *argv []) {
int c; /* Имя анализируемой опции */
int aflg = 0; /* Признак того, что задана опция a */
int bflg = 0; /* Признак того, что задана опция b */
int errflg = 0; /* Флаг наличия ошибки в командной строке */
int flg = '?'; /* Флаг (a или b), заданный в командной строке */
char *ofile = NULL; /* Указатель на аргумент опции o */
/* Подавим стандартную диагностику */
/* независимо от первого символа */
/* цепочки имен опций */
opterr = 0;
while ((c = getopt (argc, argv, ":abo:")) != -1) {
switch (c) {
case 'a':
aflg++;
flg = c;
if (bflg) {
fprintf (stderr, "Опции a и b несовместимы\n");
errflg++;
}
break;
case 'b':
bflg++;
flg = c;
if (aflg) {
fprintf (stderr, "Опции a и b несовместимы\n");
errflg++;
}
break;
case 'o':
ofile = optarg;
break;
case ':':
fprintf (stderr, "Отсутствует аргумент опции -%c\n", optopt);
errflg++;
break;
case '?':
fprintf (stderr, "Недопустимая опция -%c\n", optopt);
errflg++;
break;
}
}
if (errflg) {
(void) fprintf (stderr, "Использование: %s: [-a | -b] [-o выходной_файл] "
"[аргумент ...]\n", argv [0]);
return (1);
}
printf ("Заданный флаг: %c\n", flg);
printf ("Аргумент опции o: %s\n", ofile);
printf ("Остаток командной строки:");
for (; optind < argc; optind++) {
printf (" %s", argv [optind]);
}
printf ("\n");
return 0;
}
Приведенная программа отличается от shell-процедуры cmd более тщательным анализом ошибочных ситуаций и детальной диагностикой.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.