ИНТЕРПРЕТАТОР SHELL

PAGE 45

Лекция №4-5

ИНТЕРПРЕТАТОР SHELL

ПОСЛЕДОВАТЕЛЬНОСТЬ ДЕЙСТВИЙ ИНТЕРПРЕТАТОРА SHELL

Shell выполняет определенную последовательность действий по интерпретации команд для каждой прочитанной им строки. Зная эту последовательность, можно изолировать многие проблемы на соответствующем уровне. Иногда на определенном шаге этой последовательности могут появиться странные вхождения символов. Поэтому важно знать, когда изменяются значения переменных.

Один из примеров относится к раскрытию имени файла и символу звездочки. Shell НЕ раскрывает метасимволы во время присвоения значений. Оператор F=* означает в действительности, что переменной F присваивается один символ. Когда выполняется шаг 7 последовательности действий, звездочка раскрывается как метасимвол имени файла, превращаясь во все имена файлов в текущем каталоге. Это можно продемонстрировать на примере команды "echo $F". Для того чтобы сохранить литеральное значение звездочки, нужно экранировать ее, что защитит ее от шага 7. Получается команда echo "$F". А для того чтобы вообще подавить значение переменной F, следует исключить шаг 3 - подстановку параметров. Указывая команду echo '$F', вы печатаете буквы $F, а не значение переменной F.

Действия выполняются в таком порядке:

  1. Чтение входа и синтаксический разбор
  2. Словесное сопровождение разбора
  3. Подстановка параметров
  4. Подстановка команд
  5. Переназначение ввода-вывода
  6. Обработка внутренних разделителей полей
  7. Раскрытие имени файла
  8. Трассировка выполнения
  9. Обработка переменных среды
  10. Выполнение команды

Далее эти шаги рассматриваются более подробно. Когда пользователь программирует на языке shell, то он пытается представить, какой шаг используется для каждой строки текста программы. Зная порядок шагов, пользователь можете проще определять эффект от действия команд и сократить число возможных ошибок.

  1. Командная строка или логическая конструкция читается с терминала или из файла данных. Чтение останавливается при обнаружении следующих символов: точки с запятой (;), символа фоновой задачи (&), логического и (&&), логического или (||) либо символа новой строки (\n). Выполняется разбор введенных данных на слова с учетом пробелов и табуляций.
  2. Если в текущем интерпретаторе shell установлен флаг многословности (-v), то прочитанная строка отображается на стандартное устройство регистрации ошибок.
  3. Выполняется подстановка параметров. Сюда входит подстановка позиционных параметров, подстановка переменных и подстановка специальных выражений. Параметры всегда имеют префикс в виде денежного знака ($).
  4. Выполняется подстановка команд. Это относится ко всем командам, взятым в символы ударения (`). Такая команда вычисляется и выполняется, а результирующий текст заменяет исходное выражение в полной командной строке. Выполняемая команда может содержать последовательные команды, конвейеры или команды, сгруппированные в скобках. Любые лишние пробелы, табуляции или символы новой строки, появившиеся в результате выполнения команды, впоследствии удаляются при обработке внутренних разделителей полей. Если нужно сохранить эти лишние символы, необходимо применить двойные кавычки вокруг всего выражения.
  5. Проверяется переназначение ввода-вывода. Если таковое имеется, исходный дескриптор файла (0, 1 или 2) закрывается, а затем открывается повторно с новым значением. Вновь открытый дескриптор файла занимает в файловом вводе-выводе то же место, что и закрытый файловый дескриптор. Символы переназначения удаляются из командной строки.
  6. Поскольку командная строка могла измениться по сравнению со своим исходным состоянием в результате подстановок, она вновь разбирается на слова с учетом переменной среды IFS. Эта переменная содержит разделители между полями, которые отделяют слова друг от друга в командной строке. Каждый символ командной строки, который занесен в IFS, заменяется на пробел, чтобы разграничить слова. Все неэкранированные пробелы, табуляции, символы новой строки и нулевые аргументы удаляются из командной строки. Все экранированные значения переменных защищены от разбора, выполняемого в соответствии с IFS. Для того чтобы увидеть значение IFS, необходимо вывести такой конвейер команд, например:

echo "<$IFS>" | od -bc

  1. Далее shell ищет все слова, для которых требуется раскрытие имени файла (метасимвола). Делается попытка сопоставить образец с файлами текущего каталога. Если подходящие файлы найдены, они заменяют выражение в командной строке. Если соответствия не обнаружено, метасимволы остаются в этом выражении. Все присвоения переменным защищены от раскрытия метасимволов. Типичным примером является команда "ls z*". Если имена каких-либо файлов начинаются с буквы z, эти имена перечисляются. В противном случае печатается сообщение "z* not found".
  2. Если установлен флаг трассировки выполнения (-x), то командная строка отображается на стандартное устройство регистрации ошибок перед тем, как она будет фактически выполнена. Если это командная строка, то она выводится с префиксом "+", если же это просто присвоение значения переменной, префикса нет.
  3. На этом шаге всем переменным присваиваются значения, затем в соответствии с переменной среды PATH ищется местонахождение команды. Присвоение значений переменным происходит справа налево вдоль командной строки. Поиск по переменной PATH, наоборот, происходит слева направо. Если имя найдено, то полное маршрутное имя заменяет вызов команды в командной строке. Если переменная PATH имеет пустое значение, подразумевается текущий каталог. Если в каком-либо месте исходного имени команды имеется косая черта (/), то переменная PATH не просматривается, а считается, что указано полное маршрутное имя.
  4. Наконец, команда выполняется. Если это встроенная команда, текущий shell отрабатывает ее. В противном случае делается попытка загрузить команду в память, как если бы это была скомпилированная программа. Если эта попытка успешна, команда выполняется посредством системного вызова exec. Если загрузка команды не удалась, то считается, что это командный файл еще одного интерпретатора shell, и порождаемый shell читает этот командный файл в качестве своих данных.

СПЕЦИАЛЬНЫЕ СИМВОЛЫ SHELL-ПЕРЕМЕННЫХ

Приведенные здесь символы рассматриваются shell-переменными как специальные, так как они означают завершение слова. Для того чтобы использовать символ в его обычном значении, а не в качестве специальной функции, нужно «экранировать» его обратной косой чертой (\) или взять в одинарные кавычки.

\b

Пробел: код 20 (шестнадцатеричный), ограничитель слов

\n

Символ новой строки: ^j, код A (шестнадцатеричный), ограничитель слов

\t

Табуляция: ^i, код 9, ограничитель слов

;

Точка с запятой: завершает программный конвейер

(

Левая скобка: ограничивает подчиненный shell

)

Правая скобка: ограничивает подчиненный shell

|

Вертикальная черта, или символ программного конвейера: разделяет команды

^

Стрелка вверх, знак вставки: старый символ, используемый в качестве |

>

Правая угловая скобка (знак больше): переназначает стандартный вывод

<

Левая угловая скобка (знак меньше): переназначает стандартный ввод

&

Амперсанд: вызывает асинхронное (фоновое) выполнение

{

Левая фигурная скобка: очерчивает слово для первоначального разбора слова

}

Правая фигурная скобка: завершает знак очерчивания слова

СПЕЦИАЛЬНЫЕ СИМВОЛЫ SHELL-ОПЕРАТОРОВ

Эти символы встречаются в синтаксисе операторов языка shell. Их следует рассматривать как зарезервированные. Отдельные символы могут использоваться по-разному. Например, символ # является комментарием в операторе, а также может быть параметром, как в записи $#, означающей количество аргументов в командной строке.

&&

Двойной амперсанд: выполнить список, если программный конвейер отработал успешно

||

Двойная вертикальная черта: выполнить список в случае неудачи программного конвейера

`

Знак ударения: перехватить стандартный вывод в команде

*

При использовании в качестве параметра соответствует всем позиционным параметрам; является также символом генерации имен файлов, соответствующим любой строке

#

Комментарий до конца строки; соответствует также количеству позиционных параметров в командной строке

?

При использовании в качестве параметра соответствует статусу завершения последней синхронно выполненной команды; используется также при генерации имен файлов и означает при этом любой одинарный символ

-

Обозначает флаги, влияющие на функционирование интерпретатора shell

$

Вводит заменяемые параметры; соответствует также идентификатору процесса

!

При использовании в качестве параметра соответствует идентификатору процесса последнего фонового задания; применяется также в команде проверки, где означает "не"

"

Двойная кавычка: окаймляет символы и разрешает производить подстановку параметров

'

Одинарная кавычка: окаймляет символы, но запрещает подстановку параметров

\

Обратная наклонная черта: экранирует одиночный символ, чтобы снять его специальное значение

[]

Альтернативное использование для вызова команды проверки. Применяется также при генерации имен файлов, означая при этом диапазон символов

@

Соответствует каждому позиционному параметру командной строки

>>

Дополнить стандартный вывод

<<

Переназначить стандартный ввод на вводимые строки текста

&

Используется как символ фонового процесса; соответствует также "файловому дескриптору", если используется в переадресации

ВСТРОЕННЫЕ ОПЕРАТОРЫ ЯЗЫКА SHELL

Перечисленные ниже команды употребляются в программах на языке shell. Здесь имеется в виду Bourne shell.

Имеется также интерпретатор ksh (фактически это улучшенный Bourne shell), обладающий возможностями командных строк, характерными для csh (Си-shell). Интерпретатор ksh здесь не рассматривается.

ПРИМЕЧАНИЕ. В shell имеются команда exec и системный вызов exec. Они несут различную смысловую нагрузку и поэтому нельзя их путать. Так, команда exec обрабатывается интерпретатором shell, а системный вызов exec используется при написании программ.

КОМАНДЫ System V Bourne Shell

.

Точка: запустить данную команду из текущего интерпретатора shell, а не из порождаемого

:

Двоеточие: ничего не делать, а только возвратить успешный статус (0)

{}

Фигурные скобки: запустить последовательный список команд

break

Оборвать следующую итерацию текущего цикла

case

Многократный выбор if-then-else

cd

Сменить каталог

continue

Перейти на следующую итерацию цикла for, while или until

eval

Выполнить еще раз этап подстановки переменных

exec

Выполнить команду с аргументами, перекрывая текущий shell

exit

Остановить выполнение текущего командного файла

export

Отправить значение переменной всем подчиненным интерпретаторам shell

for

Управляющее слово в цикле for-do-done

if

Управляющее слово в последовательности if-then-else

newgrp

Изменить текущий идентификатор группы

read

Одну строку стандартного ввода присвоить переменной в качестве значения

readonly

Объявить переменную только для чтения, ее значение изменять нельзя

set

Включение и выключение флагов конфигурации shell

shift

Убрать позиционный параметр из командной строки

test

Вычислить взаимосвязи между строками и целыми числами

times

Печатать время работы процессов, запущенных из shell

trap

Определить обработчики прерываний для конкретных сигналов

ulimit

Установить предел размера файлов в 512-байтных блоках

umask

Маска прав доступа к файлам, используемая при их создании

until

Управляющее слово в цикле until-do-done

wait

Shell ждет завершения указанных порожденных процессов

while

Управляющее слово в цикле while-do-done

ДОПОЛНИТЕЛЬНЫЕ КОМАНДЫ System V.2

hash

При поиске команд использовать хэширование

name

Определить имя shell-функции

pwd

Сообщить текущий каталог; теперь это встроенная команда для ускорения

return

Выйти из shell-функции и возвратить значение

set -f

Запретить фазу генерации имен файлов

set -h

Сохранить, а не выполнять функциональные команды, если они определены

type

Определить, каким образом можно интерпретировать имя в качестве команды

unset

Удалить shell-переменные и функции

Общая характеристика командных языков

Основное назначение этих языков (их разновидностей существует достаточно много, но мы рассмотрим только три наиболее распространенные варианта - Bourne-shell, C-shell и Korn-shell) состоит в том, чтобы предоставить пользователям удобные средства взаимодействия с системой. Что это означает? Языки не даром называются командными. Они предназначены для того, чтобы дать пользователю возможность выполнять команды, предназначенные для исполнения некоторых действий операционной системы. Существует два вида команд.

Собственные команды shell (такие как cd, echo, exec и т.д.) выполняются непосредственно интерпретатором, т.е. их семантика встроена в соответствующий язык. Имена других команд на самом деле являются именами файлов, содержащих выполняемые программы. В случае вызова такой команды интерпретатор командного языка с использованием соответствующих системных вызовов запускает параллельный процесс, в котором выполняется нужная программа. Конечно, смысл действия подобных команд является достаточно условным, поскольку зависит от конкретного наполнения внешних файлов. Тем не менее, в описании каждого языка содержатся и характеристики "внешних команд" (например, find, grep, cc и т.д.) в расчете на то, что здравомыслящие пользователи (и их администраторы) не будут изменять содержимое соответствующих файлов.

Существенным компонентом командного языка являются средства, позволяющие разнообразными способами комбинировать простые команды, образуя на их основе составные команды. В семействе языков shell возможны следующие средства комбинирования. В одной командной строке (важный термин, означающий единицу информационного взаимодействия с командным интерпретатором) можно указать список команд, которые должны выполняться последовательно, или список команд, которые должны выполняться "параллельно" (т.е. независимо одна от другой).

Очень важной особенностью семейства языков shell являются возможности перенаправления ввода/вывода и организации конвейеров команд. Естественно, эти возможности опираются на базовые средства ОС UNIX. Кратко напомним, в чем они состоят. Для каждого пользовательского процесса (а внешние команды shell выполняются в рамках отдельных пользовательских процессов) предопределены три выделенных дескриптора файлов: файла стандартного ввода (standard input), файла стандартного вывода (standard output) и файла стандартного вывода сообщений об ошибках (standard error). Хорошим стилем программирования в среде ОС UNIX является такой, при котором любая программа читает свои вводные данные из файла стандартного ввода, а выводит свои результаты и сообщения об ошибках в файлы стандартного вывода и стандартного вывода сообщений об ошибках соответственно. Поскольку любой порожденный процесс "наследует" все открытые файлы своего предка, то при программировании команды рекомендуется не задумываться об источнике вводной информации программы, а также конкретном ресурсе, поддерживающим вывод основных сообщений и сообщений об ошибках. Нужно просто пользоваться стандартными файлами, за конкретное определение которых отвечает процесс-предок (заметим, что по умолчанию все три файла соответствуют вводу и выводу на тот терминал, с которого работает пользователь).

Что обеспечивает такая дисциплина работы? Прежде всего, есть возможность создания программ, "нейтральных" по отношению к источнику своих вводных данных и назначению своих выводных данных. Собственно, на этом и основаны принципы перенаправления ввода/вывода и организации конвейера команд. Все очень просто. Если вы пишете в командной строке конструкцию

com1 par1, par2, ..., parn > file_name ,

то это означает, что для стандартного вывода команды com1 будет использоваться файл с именем file_name. Если вы пишете

file_name < com1 par1, par2, ..., parn ,

то команда com1 будет использовать файл с именем file_name в качестве источника своего стандартного ввода. Если же вы пишете

com1 par1, par2, ..., parn | com2 par1, par2, ..., parm ,

то в качестве стандартного ввода команды com2 будет использоваться стандартный вывод команды com1. (Конечно, при организации такого рода "конвейеров" используются программные каналы.)

Конвейер представляет собой простое, но исключительно мощное средство языков семейства shell, поскольку позволяет во время работы динамически создавать "комбинированные" команды. Например, указание в одной командной строке последовательности связанных конвейером команд

ls -l | sort -r

приведет к тому, что подробное содержимое текущего каталога будет отсортировано по именам файлов в обратном порядке и выдано на экран терминала. Если бы не было возможности комбинирования команд, до для достижения такой возможности потребовалось бы внедрение в программу ls возможностей сортировки.

Последнее, что следует обсудить в этом пункте, это существо команд семейства языков shell. Различаются три вида команд. Первая разновидность состоит из команд, встроенных в командный интерпретатор, т.е. составляющих часть его программного кода. Эти команды предопределены в командном языке, и их невозможно изменить без переделки интерпретатора. Команды второго вида - это выполняемые программы ОС UNIX. Обычно они пишутся на языке Си по определенным правилам. Такие команды включают стандартные утилиты ОС UNIX, состав которых может быть расширен любым пользователем (если, конечно, он еще способен программировать). Наконец, команды третьего вида (так называемые скрипты языка shell) пишутся на самом языке shell. Это то, что традиционно называлось командным файлом, поскольку на самом деле представляет собой отдельный файл, содержащий последовательность строк в синтаксисе командного языка.

Базовые возможности семейства командных интерпретаторов

Здесь под командным интерпретатором будем понимать не соответствующую программу, а отдельный сеанс ее выполнения. Если в случае компилируемых программ следует различать наличие готовой к выполнению программы и факт запуска такой программы, то в случае интерпретируемых программ приходится различать случаи наличия интерпретатора, программы, которая может быть проинтерпретирована, и запуска интерпретатора с предоставлением ему интерпретируемой программы. Основным свойством динамически выполняемой программы (неважно, является ли она предварительно откомпилированной или интерпретируемой) является наличие ее динамического контекста, в частности, набора определенных переменных, содержащих установленные в них значения.

В случае командных интерпретаторов семейства shell имеется два вида динамических контекстов выполнения интерпретируемой программы. Первый вид контекста состоит из предопределенного набора переменных, которые существуют (и обладают значениями) независимо от того, какая программа интерпретируется. В терминологии ОС UNIX этот набор переменных называется окружением или средой сеанса выполнения командной программы. С другой стороны, программа при своем выполнении может определить и установить дополнительные переменные, которые после этого используются точно так же, как и предопределенные переменные. Спецификой командных интерпретаторов семейства shell (связанной с их ориентацией на обеспечение интерфейса с операционной системой) является то, что все переменные shell-программы (сеанса выполнения командного интерпретатора) являются текстовыми, т.е. содержат строки символов.

Любая разновидность языка shell представляет собой развитый компьютерный язык. Далее постараемся кратко познакомиться с особенностями трех распространенных вариантов языка shell.

Bourne-shell

Bourne-shell является наиболее распространенным командным языком (и одновременно командным интерпретатором) системы UNIX. Вот основные определения языка Bourne-shell (конечно, мы приводим неформальные определения, хотя язык обладает вполне формализованным синтаксисом):

  • Пробел - это либо символ пробела, либо символ горизонтальной табуляции.
  • Имя - это последовательность букв, цифр или символов подчеркивания, начинающаяся с буквы или подчеркивания.
  • Параметр - имя, цифра или один из символов *, @, #, ?, -, $, !.
  • Простая команда - последовательность слов, разделенных пробелами. Первое слово простой команды - это ее имя, остальные слова - аргументы команды (имя команды считается ее нулевым аргументом).
  • Метасимволы. Аргументы команды (которые обычно задают имена файлов) могут содержать специальные символы (метасимволы) "*", "?", а также заключенные в квадратные скобки списки или указанные диапазоны символов. В этом случае заданное текстовое представление параметра называется шаблоном. Указание звездочки означает, что вместо указанного шаблона может использоваться любое имя, в котором звездочка заменена на произвольную текстовую строку. Задание в шаблоне вопросительного знака означает, что в соответствующей позиции может использоваться любой допустимый символ. Наконец, при использовании в шаблоне квадратных скобок для генерации имени используются все указанные в квадратных скобках символы. Команда применяется в цикле для всех осмысленных сгенерированных имен.
  • Значение простой команды - это код ее завершения, если команда заканчивается нормально, либо 128 + код ошибки, если завершение команды не нормальное (все значения выдаются в текстовом виде).
  • Команда - это либо простая команда, либо одна из управляющих конструкций (специальных встроенных в язык конструкций, предназначенных для организации сложных shell-программ).
  • Командная строка - текстовая строка на языке shell.
  • shell-процедура (shell-script) - файл с программой, написанной на языке shell.
  • Конвейер - последовательность команд, разделенных символом "|". При выполнении конвейера стандартный вывод каждой команды конвейера, кроме последней, направляется на стандартный вход следующей команды. Интерпретатор shell ожидает завершения последней команды конвейера. Код завершения последней команды считается кодом завершения всего конвейера.
  • Список - последовательность нескольких конвейеров, соединенных символами ";", "&", "&&", "||", и, может быть, заканчивающаяся символами ";" или "&". Разделитель между конвейерами ";" означает, что требуется последовательное выполнение конвейеров; "&" означает, что конвейеры могут выполняться параллельно. Использование в качестве разделителя символов "&&" (и "||") означает, что следующий конвейер будет выполняться только в том случае, если предыдущий конвейер завершился с кодом завершения "0" (т.е. абсолютно нормально). При организации списка символы ";" и "&" имеют одинаковые приоритеты, меньшие, чем у разделителей "&&" и "||".
  • В любой точке программы может быть объявлена (и установлена) переменная с помощью конструкции "имя = значение" (все значения переменных - текстовые). Использование конструкций $имя или ${имя} приводит к подстановке текущего значения переменной в соответствующее слово.
  • Предопределенными переменными Bourne-shell, среди прочих, являются следующие:
  • HOME - полное имя домашнего каталога текущего пользователя;
  • PATH - список имен каталогов, в которых производится поиск команды, при ее указании коротким именем;
  • PS1 - основное приглашение shell ко вводу команды;

и т.д.

  • Вызов любой команды можно окружить одиночными символами – «ударение» (`), и тогда в соответствующую строку будет подставлен результат стандартного вывода этой команды.
  • Среди управляющих конструкций языка содержатся следующие: for и while для организации циклов, if для организации ветвлений и case для организации переключателей (естественно, все это специфически приспособлено для работы с текстовыми значениями).

C-shell

Командный язык C-shell главным образом отличается от Bourne-shell тем, что его синтаксис приближен к синтаксису языка Си (это, конечно, не означает действительной близости языков). В основном, C-shell включает в себя функциональные возможности Bourne-shell. Если не вдаваться в детали, то реальными отличиями C-shell от Bourne-shell является поддержка протокола (файла истории) и псевдонимов.

В протоколе сохраняются введенные в данном сеансе работы с интерпретатором командные строки. Размер протокола определяется установкой предопределенной переменной history, но последняя введенная командная строка сохраняется всегда. В любом месте текущей командной строки в нее может быть подставлена командная строка (или ее часть) из протокола.

Механизм псевдонимов (alias) позволяет связать с именем полностью (или частично) определенную командную строку и в дальнейшем пользоваться этим именем.

Кроме того, в C-shell по сравнению с Bourne-shell существенно расширен набор предопределенных переменных, а также введены более развитые возможности вычислений (по-прежнему, все значения представляются в текстовой форме).

Korn-shell

Если C-shell является синтаксической вариацией командного языка семейства shell по направлению к языку программирования Си, то Korn-shell - это непосредственный последователь Bourne-shell.

Если не углубляться в синтаксические различия, то Korn-shell обеспечивает те же возможности, что и C-shell, включая использование протокола и псевдонимов.

Реально, если не стремиться использовать командный язык как язык программирования (это возможно, но по мнению автора, неоправданно), то можно пользоваться любым вариантом командного языка, не ощущая их различий.

СИНТАКСИС ИНТЕРПРЕТАТОРА SHELL

элемент: слово

ввод-вывод

имя = значение

простая_команда: элемент

элемент простой_команды

команда: простая_команда

( список-команд )

{ список-команд }

for имя do список-команд done

for имя in слово do список-команд done

while список-команд do список-команд done

until список-команд do список-команд done

case слово in case-часть esac

if список-команд then список-команд else-часть fi

конвейер: команда

конвейер | команда

andor: конвейер

andor && конвейер

andor || конвейер

список-команд: andor

список-команд ;

список-команд &

список-команд ; andor

список-команд & andor

ввод-вывод: > файл

< файл

<< слово

>> файл

цифра > файл

цифра < файл

цифра >> файл

файл: слово

& цифра

& -

case-часть: шаблон ) список-команд ;;

else-часть: elif список-команд then список-команд else-часть

else список-команд

пустая-строка

пустая-строка:

слово: последовательность символов кроме пробелов

имя: последовательность букв, цифр или знаков подчеркивания, начинающаяся с буквы

цифра: 0 1 2 3 4 5 6 7 8 9

Метасимволы и зарезервированные слова

1. Синтаксические

| символ канала

&& символ и-если

|| символ или-если

; разделитель команд

;; разделитель регистров

& фоновые команды

() группирование команд

< перенаправление ввода

<< ввод из документа

> создание вывода

>> добавление к выводу

# комментарий в конце строки

2. Шаблоны

* отображает любой символ(ы) включая и его отсутствие

? отображает любой одиночный символ

[...] отображает любые символы в скобках

3. Подстановки

${...} подстановка переменной оболочки

`...` подстановка вывода команд

4. Квотирование

\ квотирует следующий символ как литерал без специального значения.

'...' квотирует символы в кавычках за исключением обратной кавычки.

"..." квотирует символы в кавычках за исключением $`\"

5. Зарезервированные слова

if esac

then for

else while

elif until

fi do

case done

in {}

В UNIX благодаря Shell команды могут:

- объединяться для образования новых команд;

- передавать позиционные параметры;

- добавляться или переименоваться пользователем;

- выполняться внутри циклов или по определенному условию;

- создаваться для локального выполнения без риска вступления

в конфликт с командами других пользователей;

- выполняться в фоновом режиме.

Более того, команды могут перенаправлять ввод исходных данных для команды от одного источника к другому и перенаправлять вывод в файл, на терминал, принтер или другой команде. Это обеспечивает необходимую гибкость при выполнении конкретных задач.

Большинство символов в шаблонах обозначают сами себя, но в UNIX существуют также и специальные символы, которые можно включать в шаблоны. Ими являются:

- звездочка (*), обозначающая любую последовательность символов, включая и последовательность нулевой длины;

- знак вопроса (?), который соответствует любому символу;

- квадратные скобки ([ ]), означающие любой из заключенных в них символов.

Внутри квадратных скобок пара символов, разделенных дефисом, соответствует любому символу из определяемого ими диапазона. Например, [a-de] эквивалентно [abcde].

Примеры использования метасимволов:

Метасимвол Значение

-------------------------------------------------------------

* Соответствует любому имени в текущем каталоге

*temp* Соответствует всем именам, содержащим "temp"

[a-f]*

*.c

/usr/bin/? Соответствует всем именам файлов в каталоге

/usr/bin, состоящих из одного символа

Использование шаблонов позволяет избежать лишнего ввода с клавиатуры, и, что более существенно, дает возможность объединять связанные между собой информационные файлы в группы, используя общие символы в именах или расширениях.

Следует заметить, что имена каталогов не должны включать в себя следующие символы:

* ? [ ]

Использование кавычек

Имеется несколько символов, которые имеют в Shell специальное значение. Это <, >, *, ?, [ и ]. Чтобы отменить специальное значение этих символов, требуется заключать их особым образом в одинарные (') или двойные (") кавычки. То же действие в отношении одиночного символа выполняет обратная наклонная черта (\). (Обратные одинарные кавычки (`) используются только для командной подстановки в Shell и не отменяют специальных значений символов.)

Все символы, заключенные в одинарные кавычки, воспринимаются как обычные текстовые символы. Например, строка:

echostuff='echo $? $*; ls *| wc'

только присваивает переменной echostuff значение

echo $? $*; ls *| wc

но не вызывает выполнение ни одной из команд.

Внутри двойных кавычек специальное значение сохраняется только у некоторых символов - это $, \, ` и ". В результате внутри двойных кавычек имеет место подстановка значений переменных и команд. Однако, сами по себе двойные кавычки никак не влияют на подставляемые команды, и поэтому такие символы, как *, сохраняют свое специальное значение.

Чтобы отменить специальное значение символа $ и одинарных и двойных кавычек внутри двойных кавычек, перед ними требуется дополнительно указывать символ \. Вне двойных кавычек указание символа \ перед специальным символом адекватно заключению этого специального символа в одинарные кавычки. Символ \, стоящий перед символом новой строки, обозначает игнорирование этого символа.

Благодаря этому можно продлевать командную строку.

Ниже приводятся несколько примеров использования кавычек:

Ввод

Интерпретация

'`'

обратная кавычка

'"'

двойная кавычка

'`echo one`'

одно слово "`echo one`"

"\""

двойная кавычка

"`echo one`"

слово one

"`"

cинтаксическая ошибка

one two

два слова one и two

"one two"

одно слово "one two"

'one two'

одно слово "one two"

'one * two'

одно слово "one * two"

"one * two"

одно слово "one * two"

`echo one`

одно слово "one"

Командные строки и конвейеры

Последовательность команд, разделенных вертикальными чертами (|), образует конвейер. В конвейере, состоящем более чем из одной команды, каждая команда выполняется как отдельный процесс связанный с соседними с помощью программных каналов - т.е. вывод каждой команды (за исключением последней) становится вводом для следующей команды в строке.

Фильтр - это команда, которая считывает стандартный ввод, преобразует его некоторым образом и затем записывает в стандартный вывод. Конвейер, как правило, состоит из последовательности фильтров. Несмотря на то, что процессы в конвейере могут выполняться параллельно, каждой программе необходимо прочитать вывод предыдущей. Многие команды работают с отдельными строками текста - считывают их, обрабатывают, записывают и возвращаются назад для считывания новых строк. Некоторые команды должны наоборот сначала считать большой объем данных и потом только начинают выводить результаты - sort является примером такой команды, которой требуется вначале считать все входные данные и затем только будет проводиться обработка. Вот пример типичного конвейера:

nroff -mm text| col| lpr

Здесь nroff - это форматер текста, имеющийся в Системе Обработки Текстов UNIX, col - преобразует вывод для конкретного типа дисплея и lpr осуществляет печать текста. Флаг -mm обозначает одну из наиболее часто употребляемых опций форматирования, и text - это имя файла, который должен быть отформатирован. Следующие примеры демонстрируют множество возможностей, которые могут быть получены комбинированием команд описанными выше способами. Попробуйте выполнить их :

* who

Печатает на экране список зарегистрированных в системе пользователей.

* who >> log

Добавляет список загруженных пользователей в конец файла log.

* who| wc -l

Печатает количество зарегистрированных пользователей.

* who| pr

Постранично печатает список пользователей.

* who| sort

Печатает список пользователей в алфавитном порядке.

* who| grep bob

Печатает список пользователей, чьи имена содержат слово

bob.

* who| grep bob| sort| pr

Печатает постранично в алфавитном порядке список пользователей, чьи имена содержат слово bob.

* { date;who | wc -l; } >> log

Добавляет в файл log текущую дату и количество зарегистрированных в системе пользователей. Обратите внимание на пробелы после левой фигурной скобки и перед правой фигурной скобкой.

* who| sed -e 's/ .*//'| sort| uniq -d

Печатает имена только тех пользователей, которые регистрировались в системе более одного раза. Обратите внимание на использование sed в качестве фильтра для удаления символов, стоящих после имени пользователей.

Команда who сама по себе не имеет возможности получать все эти результаты - их можно получить только объединив who с другими командами. Команда who в этих примерах служит как источник информации. В качестве упражнения замените "who|" на "</etc/passwd" и убедитесь в том, что файл может также быть использован как источник данных. Помните, что аргументы переадресации могут находиться в любом месте в командной строке, даже в самом ее начале. Это значит, что конвейер

< infile >outfile sort| pr

аналогичен

sort < infile |pr >outfile

Командная подстановка

Любая командная строка может быть помещена внутрь обратных кавычек (`....`), в результате чего вывод этой командной строки заменит при выполнении саму эту строку. Это называется командная подстановка. Команда или команды, заключенные в обратные кавычки, обрабатываются Shell в первую очередь и их вывод заменяет сами эти команды и обратные кавычки. Это свойство часто используется для присвоения значений переменным Shell. (Переменные Shell рассматриваются в следующем разделе.)

Например:

today=`date`

присваивает строку со значением текущей даты переменной today (например "Tue Nov 26 16:01:09 EST 1985"). Следующая команда сохраняет число зарегистрированных в системе пользователей в переменной users:

users=`who | wc -l`

Любая команда, которая пишет в стандартный вывод, может быть заключена в обратные кавычки. Обратные кавычки могут быть вложены одни в другие, но при этом внутренние должны отделяться обратными косыми чертами (\), например:

logmsg=`echo Your login directory is \`pwd\``

покажет строку "Your login directory is .(имя вашего каталога)". Переменным Shell также можно присваивать значения, используя команды read и line. Команда read считывает строку из стандартного ввода (обычно с терминала) и присваивает отдельные слова всем указанным переменным.

Например, предположим, что команда:

read first init last

прочитала строку вида:

G. A. Snyder

Тогда указанным переменным будут присвоены значения:

first=G. init=A. last=Snyder

Все "лишние" прочитанные слова команда read присваивает последней переменной.

Команда line считывает строку из стандартного ввода и повторяет в стандартном выводе.

Переменные Shell

В Shell имеется несколько способов создания переменных. Переменная - это имя, которому присвоено текстовое значение. Некоторые переменные являются позиционными параметрами - их значения устанавливаются только в командной строке. Остальные переменные - это просто имена, которым пользователь или сам интерпретатор присвоил текстовые значения.

Позиционные параметры (переменные)

В среде Shell, из 128 существующих позиционных переменных доступны по именам только 10, это $0, $1, $2, ….,$9. Все остальные расположены в стеке и доступны только после специальной манипуляции, причем действе не имеет обратной силы.

Когда вызывается процедура Shell, то Shell сразу неявно создает позиционные параметры. Имя самой процедуры Shell - в нулевой позиции, в командной строке, присваивается позиционному параметру $0. Первый аргумент команды называется $1 и т.д. Чтобы получить доступ к аргументам в позициях с номером больше 9, можно использовать команду shift. Например, следующую процедуру можно использовать для просмотра позиционных параметров и затем обрабатывать все необходимые файлы:

while test -n "$1"

do case $1 in

-a) A=aoption ; shift;;

-b) B=boption ; shift;;

-c) C=coption ; shift;;

-*) echo "bad option" ; exit 1 ;;

*) process rest of files

esac

done

Пользователь может явно передать необходимые значения этим позиционным параметрам с помощью команды set. Например, команда:

set abc def ghi

присваивает значение "abc" первому позиционному параметру $1, значение "def" - второму $2 и "ghi" - третьему $3. Нулевой позиционный параметр $0 нельзя переопределить таким способом - он всегда относится к имени процедуры Shell.

Переменные, определяемые пользователем

Интерпретатор Shell также распознает буквенно-цифровые переменные, которым присвоены текстовые значения. Простая команда присвоения имеет синтаксис:

имя=строка

После присвоения при указании $имя будет выдаваться значение строки. Имя переменной - это последовательность букв, цифр и знаков подчеркивания, обязательно начинающееся с буквы или знака подчеркивания. Пробелы вокруг знака равенства (=) в команде присваивания недопустимы. Позиционные параметры таким способом определить нельзя; они могут устанавливаться только командой set. В команде присвоения может быть более одного присвоения, но помните, что Shell присваивает значения переменным справа налево. Так, в результате выполнения следующей командной строки:

A=$B B=abc

переменной А будет присвоено значение "abc".

Ниже приводятся примеры простых присвоений. Двойные кавычки с правой стороны команды присвоения позволяют указывать пробелы, знаки табуляции, символы перехода на новую строку и точки с запятой в присваиваемом тексте, а также выполнять подстановку переменных. Это значит, что при обнаружении позиционных параметров и других имен переменных, перед которыми стоит знак $, они будут заменяться на соответствующие значения, если они имеются. Одинарные кавычки подавляют подстановку переменных:

MAIL=/usr/mail/gas

echovar="echo $1 $2 $3 $4"

stars=*****

asterisks='$stars'

В приведенных примерах переменной echovar присвоено значение строки, состоящей из слова echo и четырех позиционных параметров, разделенных пробелами. Вокруг последовательности звездочек, присваиваемых переменной stars, кавычек не требуется, т.к. их специальное значение здесь не действует. Обратите внимание, что переменной asterisks присвоено текстовое значение "$stars", а не "*****", т.к. одинарные кавычки отменили подстановку.

В командах присвоения при подстановке пробелы повторно не интерпретируются, поэтому переменные $first и $second в следующем примере имеют одно и то же значение:

first='a string with embedded spaces'

second=$first

При обращении к значениям переменных вы можете заключать имя переменной в фигурные скобки, чтобы отделить имя переменной от остального текста. В частности, если символ, непосредственно следующий после имени, является буквой, цифрой или знаком подчеркивания, то фигурные скобки необходимы. В качестве примера рассмотрим:

a='This is a string'

echo "${a}ent test of variables."

В данном случае команда echo напечатает:

---------------------------------------------------------

¦ This is a stringent test of variables.

¦

Если бы вы не использовали фигурные скобки, то Shell подставил бы пустое значение вместо "$aent" и напечатал:

---------------------------------------------------------

¦ test of variables.

¦

Переменные поддерживаемые интерпретатором

Интерпретатором Shell поддерживаются следующие переменные. Некоторые из них определяются и устанавливаются самим интерпретатором, но все они могут быть изменены пользователем.

HOME Программа login присваивает этой переменной значение каталога загрузки пользователя, т.е. каталога, который является текущим во время выполнения login; команда cd, указанная без аргументов, переключает в каталог с именем $HOME. Использование этой переменной позволяет не следить за полными именами процедур Shell.

IFS Эта переменная определяет, какие символы являются внутренними разделителями полей. Это символы, которые Shell использует при интерпретации пробелов. (Если вы хотите использовать какой-либо разделитель, вы должны установить его в переменной IFS.) Интерпретатор Shell при запуске включает в IFS символ пробела, знак табуляции и символ новой строки.

MAIL Эта переменная содержит полное имя (с путями, где он расположен) файла, куда приходит почта. Если переменная MAIL установлена, то Shell проверяет, было ли что-нибудь добавлено в указанный файл и дает сообщение о появлении новой почты при каждом возврате на командный уровень (например, при выходе из редактора). Переменная

MAIL автоматически не устанавливается; если требуется, ее необходимо установить в пользовательском файле .profile. (Команда export и файл .profile рассматриваются ниже.) (О наличии почты в стандартном файле почты сообщается при загрузке независимо от значения переменной MAIL.)

MAILCHECK Этот параметр определяет с какой частотой (в секундах) Shell будет проверять появление почты в файлах, указанных в параметрах MAILPATH и MAIL. Принимаемое по умолчанию значение равно 600 секунд. Если этот параметр установлен равным 0, то Shell будет проверять наличие почты при каждом появлении символа приглашения.

MAILPATH Этот параметр содержит список имен файлов, разделенных двоеточием. Если этот параметр установлен, то Shell информирует пользователя о приходе почты в любой из указанных файлов. После каждого имени файла может стоять знак % и сообщение, которое будет выводиться, когда произойдет изменение времени модификации этого файла. По умолчанию выдается сообщение "You have mail".

SHACCT Если в этот параметр занесено имя файла, доступного пользователю для записи, Shell будет делать в него соответствующие записи для каждой выполняемой процедуры Shell. Для анализа этих данных могут использоваться такие процедуры, как acctcom (ADM) и accton (ADM).

SHELL Когда вызывается интерпретатор Shell, он ищет эту переменную и если она имеется и в ее значении вместо имени файла указан символ 'r', то Shell загружается в укороченном виде.

PATH Эта переменная определяет пути, которые использует Shell при поиске команд. Ее значение представляет собой список имен каталогов, разделенный двоеточиями. Shell присваивает переменной PATH значение :/bin:/usr/bin , где перед первым двоеточием ничего не указано. Пустой аргумент в любом месте в списке путей всегда обозначает текущий каталог. В некоторых системах поиск в текущем каталоге по умолчанию отсутствует, и поэтому переменная PATH определяется как /bin:/usr/bin. Если вы хотите, чтобы текущий каталог при поиске команд просматривался последним, а не первым, используйте:

PATH=/bin:/usr/bin:

В следующем примере два двоеточия, стоящие подряд, интерпретируются как двоеточие, за которым следует пустой аргумент и двоеточие, тем самым обозначая текущий каталог. Вы можете создать личный каталог с командами (например, $HOME/bin) и задать просмотр его в первую очередь:

PATH=$HOME/bin::/bin:/usr/bin

Параметр PATH обычно устанавливается в файле .profile.

CDPATH Эта переменная определяет пути поиска каталогов, содержащих arg. Альтернативные имена каталогов разделяются двоеточиями. Путь, используемый по умолчанию, - <null> (он определяет текущий каталог). Текущий каталог определяется нулевым именем пути, который можно указывать сразу после знака равенства или между двумя двоеточиями в любом месте списка. Если arg начинается с /, тогда поиск по путям не выполняется. В противном случае просматриваются все указанные в путях каталоги.

PS1 Эта переменная определяет, какое выражение следует использовать в качестве первичного приглашения системы. Если Shell является интерактивным, то он выдает приглашение в виде PS1, когда ожидается ввод. По умолчанию PS1 присвоено значение "$ ".

PS2 Эта переменная определяет выражение для вторичного приглашения системы. Если интерпретатору Shell требуется входных данных больше, чем было указано в строке, то он выдает приглашение в виде PS2. По умолчанию PS2 присвоено значение "> " (знак "больше", за которым следует пробел).

В большинстве случаев вам необходимо передавать все указанные переменные во все интерпретаторы, которые вы используете. Для этого используйте команду export в вашем файле .profile, например:

export HOME IFS MAIL PATH PS1 PS2

Заранее определяемые специальные переменные

У некоторых переменных есть специальное значение и оно устанавливается только самим интерпретатором. Это:

$# Записывает количество аргументов, переданных в Shell, не считая имени самой вызываемой процедуры. Например, при выполнении команды :

sh cmd a b c

в $# передается номер последнего позиционного параметра и поэтому $# равно 3. Эту переменную можно использовать, например, для проверки наличия всех необходимых аргументов:

if test $# -lt 2

then

echo 'two or more args required'; exit

fi

$? Эта переменная содержит код завершения последней выполненной команды. Ее значение это десятичное число. Большинство команд UNIX дают код возврата 0 при успешном завершении. Сам Shell возвращает текущее значение $? В качестве своего кода завершения.

$$ В этой переменной хранится номер текущего процесса. Поскольку номера процессов среди всех выполняемых никогда не могут совпадать, эту переменную часто используют для образования имен временных файлов. В UNIX нет механизма автоматической генерации и удаления временных файлов. Файл существует до тех пор, пока его не сотрут специально. Пользоваться временными файлами как правило бывает неудобно - механизм программных каналов UNIX для большинства приложений намного лучше. Однако, потребность во временных файлах с уникальными именами иногда все же появляется.

Следующий пример демонстрирует рекомендуемый способ создания временных файлов. Обратите внимание на то, что каталоги /bin и /usr/tmp очищаются в случае перезагрузки системы.

# применение идентификатора текущего

# процесса для формирования уникального

# временного файла

temp=/usr/tmp/$$

ls > $temp

# здесь стоят команды, некоторые из которых

# используют временные файлы

rm -f $temp

# стирание в конце работы

$! Номер последнего процесса, который выполнялся в фоновом режиме. Это число, содержащее от 1 до 5 цифр.

$- Это переменная, состоящая из имен флагов, включенных в данный момент в Shell.

Состояние Shell

Состояние Shell в каждый момент времени определяется значениями позиционных параметров, переменных, определенных пользователем, переменными операционной системы, режимом работы и текущим рабочим каталогом.

Состояние Shell может быть изменено различными способами. Они включают в себя смену текущего каталога с помощью команды cd, установку флагов и чтение команд из специального файла .profile, находящегося в вашем загрузочном каталоге.

Смена каталогов

Команда cd меняет каталог на тот, который указан в качестве аргумента. Ее можно и нужно использовать для перехода в требуемое место в структуре каталогов. Обратите внимание, что cd часто помещают внутри скобок, чтобы приказать подоболочке сменить каталог и выполнить какие-либо команды, не затрагивая основную оболочку.

Например, первая из приводимых ниже команд копирует файл /etc/passwd в /usr/you/passwd, а вторая сначала меняет каталог на /etc и затем копирует файл:

cp /etc/passwd /usr/you/passwd

(cd /etc; cp passwd /usr/you/passwd)

Обратите внимание на скобки. Обе строки имеют одинаковый эффект.

Если интерпретатор считывает команды с терминала и указанного каталога не существует (или доступ запрещен), то проводится попытка коррекции каждого компонента имени каталога в поисках "правильного" имени. После этого Shell спрашивает разрешения на переход в каталог с исправленным именем: ответ "n" означает запрет, а любой другой символ разрешает.

Файл .profile

Файл с именем .profile считывается каждый раз, когда вы загружаетесь в UNIX. Он обычно используется для выполнения специальных команд и для установки и передачи переменных во все подоболочки. Только после того, как все команды из файла .profile будут считаны и выполнены, Shell начнет считывание команд из стандартного ввода - обычно с терминала. Если вы хотите произвести переустановку параметров среды после внесения изменений в файл .profile, введите:

.profile

С помощью этой команды можно избежать перерегистрации пользователя в системе, обычно используемой для исполнения файла .profile.

Флаги выполнения

Команда set позволяет вам изменять поведение Shell путем установки определенных флагов. В частности, флаги -x и -v могут быть полезны при вызове Shell как команды с терминала. Эти флаги можно установить командой set:

set -xv

Те же самые флаги можно выключить командой:

set +xv

Эти два флага имеют следующее действие:

-v Вводимые строки печатаются так, как они считываются Shell'ом. Этот флаг удобен для устранения синтаксических ошибок. Команды на каждой введенной строке выполняются после того, как она будет напечатана.

-х Команды и их аргументы печатаются по мере их выполнения. (Команды управления Shell, такие как for, while и т.д. не печатаются.) Обратите внимание, что -х вызывает трассировку только тех команд, которые выполняются, а -v печатает всю командную строку, пока не встретится синтаксическая ошибка.

Команда set также используется для установки этих и других флагов в процедурах Shell.

Среда выполнения команд

Все переменные и их значения, которые известны команде перед выполнением, образуют ее оболочку (среду выполнения). Эта оболочка включает в себя переменные, которые команда получает "по наследству" от родительского процесса, и переменные, указанные как ключевые параметры в командной строке, которая запускает команду. Переменные, которые Shell передает дочернему процессу - это переменные, перечисленные как аргументы в команде export. Эта команда помещает указанные переменные в оболочки как самого Shell, так и всех его будущих дочерних процессов.

Ключевые параметры - это пары переменная-значение, которые появляются в форме присвоений, обычно перед именем процедуры в командной строке. Такие переменные помещаются в оболочку вызываемой процедуры. Например:

# keycommand

echo $a $b

Это пример простой процедуры, которая выводит значения двух переменных. Если ее вызвать следующим образом:

a=key1 b=key2 keycommand

то результирующий вывод будет:

key1 key2

Ключевые параметры не являются аргументами и не изменяют $#. Процедура имеет доступ к значению любой переменной в своей оболочке. Однако, даже если значение переменной было изменено, эти изменения не отражаются на параметрах Shell; они относятся только к конкретной процедуре. Для того, чтобы эти изменения были переданы в дочерние процессы данной процедуры, эти переменные должны быть перечислены в команде export в этой процедуре. Чтобы получить список переменных, которые являются передаваемыми из текущего интерпретатора Shell, введите:

export

Вы также получите список переменных, которые можно только считывать. Чтобы получить список пар имя-значение в текущей оболочке, введите:

printenv

или

env

Вызов интерпретатора Shell

Shell - это команда и она может быть вызвана также как и любая другая команда:

sh proc [arg...] Новая копия Shell вызывается для чтения proc.

sh -v proc [arg...] Это эквивалентно помещению "set -v" в начале proc. Также можно указывать флаги -x, -e, -u, -n.

proc [arg...] Если proc является выполняемым файлом, не скомпилированной программой, то эффект будет такой же, как от команды:

sh proc args

Преимущество такой формы записи заключается в том, что переменные, которые были экспортированы в оболочку, будут передаваться и в процессы, запускаемые из proc. Таким образом, любые изменения, произведенные в отношении передаваемых переменных внутри proc, будут распространяться на последующие команды, вызываемые из proc.

Передача параметров в процедуры Shell

Когда просматривается командная строка, любая последовательность символов в виде $n заменяется на n-ный аргумент Shell, считая имя процедуры как $0. Такое соглашение позволяет производить прямое обращение к имени процедуры и к позиционным параметрам (не более 9). Дополнительные аргументы можно обрабатывать, пользуясь командой shift или используя цикл for.

Команда shift сдвигает аргументы влево, т.е. значение $1 теряется, $2 заменяет $1, $3 заменяет $2 и т.д. Позиционный параметр с наибольшим номером становится неопределенным ($0 никогда не сдвигается). Например, в приводимой ниже процедуре ripple, команда echo записывает свои аргументы в стандартный вывод:

# команда ripple

while test $# != 0

do

echo $1 $2 $3 $4 $5 $6 $7 $8 $9

shift

done

Строки, начинающиеся с #, являются комментариями. Команда цикла while рассматривается в пункте "Условные циклы: while и until" в этой главе. Если эта процедура будет вызвана таким способом:

ripple a b c

то она напечатает:

---------------------------------------------------------

¦ a b c

¦ b c

¦ c

Специальная переменная "звездочка" ($*) вызывает подстановку всех позиционных параметров, кроме $0. Так, строку с командой echo в последнем примере можно записать намного компактнее:

echo $*

Эти две записи команды echo неэквивалентны полностью. Первая печатает максимум девять позиционных параметров, а вторая печатает все текущие позиционные параметры. Переменная ($*) более емкая и подвержена ошибкам в меньшей степени. Один из способов ее использования состоит в передаче команде произвольного числа аргументов. Например, команда:

wc $*

подсчитывает слова в каждом из файлов, указанных в командной строке.

Важно понимать последовательность действий, выполняемых Shell'ом при просмотре командной строки и выполнении подстановок. Shell сначала считывает ввод до символа новой строки или точки с запятой и затем выполняет анализ считанной части ввода. Переменные заменяются своими значениями и затем делается командная подстановка (через обратные кавычки). Затем определяются, обрабатываются и удаляются из строки аргументы, переадресующие ввод-вывод. После этого Shell просматривает получившуюся командную строку и ищет внутренние разделители полей, т.е. все символы, определенные в IFS и разделяющие командную строку на отдельные аргументы. Пустые аргументы (определенные в виде "" или '') сохраняются, а остальные пустые аргументы, полученные в результате оценки переменных, которые являются нулевыми или не определены, удаляются. Затем происходит формирование имен файлов по указанным шаблонам. И только после этого командная строка выполняется интерпретатором.

Иногда командные строки создаются внутри процедур Shell. В этом случае бывает полезно заставить Shell повторно просмотреть командную строку после выполнения подстановок. Для этих целей имеется специальная команда eval. Она считывает командную строку в качестве своего аргумента, выполняет все подстановки и печатает ее. Рассмотрим следующий пример:

command=who

output=' | wc -l'

eval $command $output

Эта часть процедуры выведет на экран получившуюся строку:

who | wc -l

Использование команды eval может быть вложенным, при этом командная строка просматривается несколько раз.

Управление потоком выполнения

Shell имеет несколько команд, которые используют множество управляющих структур, полезных при управлении потоком выполнения процедур. Перед тем как описывать эти структуры, необходимо определить некоторые термины.

Простая команда - это любая неделимая команда, определяемая именем выполняемого файла. Аргументы переадресации ввода-вывода могут указываться в простых командах и они передаются интерпретатору, а не самой команде.

Команда - это простая команда или любая из управляющих команд Shell, описанных ниже. Конвейер - это последовательность из одной или более команд, разделенных вертикальными чертами (|). В конвейере стандартный вывод каждой команды, кроме последней, соединен посредством программного канала со стандартным вводом следующей команды. Каждая команда в конвейере выполняется отдельно; Shell ждет завершения последней команды. Код завершения конвейера равен коду завершения последнего процесса в конвейере.

Список команд - это последовательность из одного или более конвейеров, разделенных точками с запятой (;), амперсандами (&), символами "и-если" (&&) или "или-если" (||) и заканчивающаяся (необязательно) точкой с запятой или амперсандом. Точка с запятой вызывает последовательное выполнение предыдущего конвейера. Это значит, что Shell ждет конца выполнения конвейера и только после этого считывает следующий. С другой стороны, амперсанд вызывает асинхронное выполнение в фоновом режиме предыдущего конвейера. Таким образом допускаются как последовательное, так и фоновое выполнение. Фоновый конвейер продолжает выполнение до тех пор, пока не завершится самостоятельно или будет уничтожен.

Другие области применения амперсанда включают в себя фоновую компиляцию и создание заданий, которые будут пересылаться другим компьютерам. Допустим, вы ввели:

nohup cc prog.c&

Вы можете продолжать работать в то время как Си-компилятор выполняется в фоновом режиме. Командная строка, заканчивающаяся амперсандом, защищена от прерываний и выхода, которые вы указываете, набирая с клавиатуры INTERRUPT или QUIT. Только одновременное нажатие клавиш Ctrl-d может снять команду в случае, если вы работаете через коммутируемую линию или имеете stty hupcl. В этом случае мы также советуем устанавливать защиту от прерываний. Для этого используется команда nohup. Допустим, что в приведенном примере вы не указали nohup, тогда если вы выходите из подзадачи до того, как завершится процесс сс, он будет уничтожен со всем своим выводом.

Амперсанд нужно применять с ограничениями, особенно в сильно загруженных системах. В противном случае возникнут претензии со стороны других пользователей, если вы будете запускать много процессов в фоновом режиме без достаточных на то оснований.

Операторы "и-если" и "или-если" (&& и ||) вызывают выполнение конвейеров при соответствии определенных условий. Оба эти оператора имеют одинаковый приоритет при расшифровке командной строки, но меньший, чем у амперсанда и вертикальной черты. В командной строке:

cmd1 || cmd2

первая команда cmd1 выполняется и анализируется ее код завершения. Команда cmd2 выполняется только в том случае, если сmd1 завершилась с кодом, не равным 0. Т.е. это является краткой записью следующих команд:

if cmd1

test $? !=0

then

cmd2

fi

Оператор "и-если" (&&) выполняет проверку на равенство. Например, в командной строке :

cmd1 && cmd2

вторая команда выполняется только в том случае, если первая завершилась успешно (код завершения равен 0). В следующей строке каждая команда выполняется по очереди до тех пор, пока какая-нибудь не закончится с кодом, не равным 0:

cmd1 && cmd2 && cmd3 && ... && cmdn

Простая команда в конвейере может быть заменена списком команд, заключенным в простые или фигурные скобки. Вывод всех команд, заключенных в скобки, объединяется в один поток, который становится входом для следующей команды в конвейере. Следующая командная строка форматирует и печатает два отдельных документа:

{ nroff -mm text1; nroff -mm text2; } | lpr

Обратите внимание на то, что после левой скобки необходим пробел, а перед правой должна стоять точка с запятой.

Использование оператора if

Shell позволяет структурировать условные переходы с помощью оператора if. Простейшая форма оператора if следующая:

if список команд

then список команд

fi

Список команд, стоящий после if, выполняется, и если код завершения последней выполненной команды равен 0, тогда выполняется список команд, указанный после then. Слово fi обозначает конец команды if.

Чтобы выполнить альтернативный набор команд при ненулевом коде завершения, используется условие else, которое записывается cледующим образом:

if список команд

then список команд

else список команд

fi

Используя условие elif вместе с командой if, можно выполнять различные условия, но при большом количестве проверок предпочтительнее использовать оператор case. Например:

if test -f "$1"

# является ли $1 файлом?

then pr $1

elif test -d "$1"

# если нет, то является ли $1 каталогом?

then (cd $1; pr *)

else echo $1 не является ни файлом, ни каталогом

fi

Приведенный пример выполняется следующим образом: если значение первого позиционного параметра является именем файла (-f), то этот файл печатается; если нет, то проверяется, является ли это имя именем каталога (-d). Если это так, то вы переходите в этот каталог (cd) и печатаете все находящиеся в нем файлы ( pr *). Во всех остальных случаях появляется сообщение об ошибке.

Команды if могут вкладываться друг в друга, но каждый if обязательно должен иметь свой fi.

Кодом завершения команды if является код завершения последней выполненной команды после условий then и else. Если команды не выполнялись, то код завершения равен 0.

Обратите внимание, что вместо указания команды test можно использовать альтернативное обозначение - прямоугольные скобки с заключенным в них проверяемым выражением. Например, предыдущий пример можно было записать так:

if [ -f "$1" ]

# является ли $1 файлом?

then pr $1

elif [ -d "$1" ]

# если нет, то является ли $1 каталогом?

then (cd $1; pr *)

else echo $1 не является ни файлом, ни каталогом

fi

Обратите внимание на то, что при такой форме записи пробелы после левой скобки и перед правой обязательны.

Использование оператора case

Оператор case также дает возможность проверять различные условия. Основной формат оператора case следующий:

case подстрока in

шаблон ) cписок команд ;;

....

шаблон ) cписок команд ;;

esac

Shell пытается найти указанную подстроку по очереди в каждом шаблоне, используя те же самые соглашения, что и при генерации имен файлов. Если соответствие установлено, то выполняется список команд после этого шаблона. Два символа точки с запятой (;;) служат обозначением конца оператора case и необходимы после каждого шаблона кроме последнего. Обратите внимание на то, что только одно значение шаблона (из указанных) может обнаружиться в подстроке, что сравнение с шаблонами выполняется последовательно, и поэтому если первым шаблоном выступает звездочка (*), все остальные игнорируются.

С одним и тем же списком команд может быть связано несколько альтернативных шаблонов, разделенных символом (|).

case $i in

*.c) cc $i

;;

*.h | *.sh)

: do nothing

;;

*) echo "$i of unknown type"

;;

esac

В этом примере для второго набора шаблонов не выполняется ни каких действий, т.к. указана пустая команда (:). Звездочка используется как шаблон, соответствующий любому слову.

Код завершения оператора case равен коду завершения последней выполненной команды. Если никакие команды не выполнялись, то код завершения равен 0.

Условные циклы: while и until

Основная форма команды while имеет вид:

while список команд

do

список команд

done

Сначала выполняются команды из первого списка, и если код завершения последней команды в этом списке равен 0, тогда выполняются команды из второго списка. Эта последовательность повторяется до тех пор, пока код завершения первого списка равен 0. Чтобы цикл выполнялся до тех пор, пока код завершения первого списка не равен 0, надо заменить while на until.

Любая строка может быть заменена символом "точка с запятой". Код завершения команд while и until равен коду завершения последней выполненной команды во втором списке. Если команды из второго списка не выполнялись, код завершения равен 0.

Организация цикла для списка: for

Часто бывает нужно выполнить некоторый набор операций для каждого файла из какого-либо списка или выполнить одну команду для каждого из нескольких аргументов. Для этого существует команда for. Формат команды for следующий:

for переменная in список значений

do

список команд

done

Значения переменных в списке разделяются пробелами. Команды в списке команд выполняются один раз для каждого значения переменной из списка значений. Переменной по очереди присваивается каждое значение из списка. Например, следующий цикл for производит сравнение исходных текстов xec.c, cmd.c и word.c в текущем каталоге с файлами с такими же именами, но в каталоге

/usr/src/cmd/sh:

for CFILE in xec cmd word

do

diff $CFILE.c /usr/src/cmd/sh/$CFILE.c

done

Обратите внимание на то, что первое упоминание имени CFILE сразу после слова for не сопровождается появлением символа "$", поскольку в данном случае используется имя переменной, но не ее значение.

Слово "in", являющееся частью команды for, можно не указывать. В этом случае текущий набор позиционных параметров будет использован вместо списка значений. Это полезно в том случае, если набор команд выполняется для каждого аргумента из списка аргументов, общее число которых неизвестно.

В качестве примера создайте файл с именем echo2, который содержит следующий текст:

for word

do

echo $word$word

done

Измените статус файла echo2:

chmod +x echo2

Теперь введите следующую команду:

echo2 ma pa bo fi yo no so ta

Результатом выполнения этой команды будет:

---------------------------------------------------------

¦ mama

¦ papa

¦ bobo

¦ fifi

¦ yoyo

¦ nono

¦ soso

¦ tata

Управление циклами: break и continue

Для прекращения выполнения циклов while или for применяется команда break. Команда continue немедленно запускает выполнение следующего шага в цикле. Эти команды действуют только если они встречаются между командами do и done.

Команда break завершает выполнение только выполняемого в данный момент цикла, вызывая продолжение работы команд, стоящих после ближайшего done. Выход из n-уровневого вложенного цикла достигается командой break n.

Команда continue продолжает выполнение с ближайшего открытого оператора for, while или until, т.е. того, который содержит этот оператор continue. Вы также можете задать аргумент n в команде continue, и тогда выполнение будет продолжено с n-ного вложенного цикла:

# Эта процедура интерактивная.

# Команды "break" и "continue" используются

# пользователем для управления вводом данных.

while true #loop forever

do echo "Please enter data"

read response

case "$response" in

"done") break

# no more data

;;

"") # just a carriage return,

# keep on going

continue

;;

*) # process the data here

;;

esac

done

Конец файла и выход

Когда Shell обнаруживает конец файла в процедуре, она завершается, возвращая в свой родительский процесс код завершения последней команды, выполненной до обнаружения конца файла. Выход из Shell самого высокого уровня производится нажатием клавиши Ctrl-d (при этом пользователь выгружается из системы).

Команда exit моделирует конец файла, устанавливая код завершения равным значению своего аргумента, если он имеется. Так, процедуру можно нормально завершить, указав в конце файла "exit 0".

Группирование команд: скобки () и []

В интерпретаторе Shell имеется два способа группирования команд - с помощью обычных и прямоугольных скобок. Обычные скобки приказывают Shell создать подоболочку, которая считывает заключенные в скобки команды. Правая и левая скобки распознаются в любом месте командной строки. В качестве литералов они интерпретируются только в случае, если заключены в кавычки. Например, если вы введете:

garble(stuff)

то Shell выдаст сообщение об ошибке. Строки с кавычками типа:

garble"("stuff")"

"garble(stuff)"

интерпретируются правильно. Правила использования кавычек рассматриваются в разделе "Использование кавычек" данной главы.

Возможность создания подоболочки путем группирования команд полезна при выполнении операций, которые не должны влиять на значения переменных в текущей оболочке или когда временно меняется рабочий каталог и команды выполняются в новом каталоге без возврата в текущий.

Параметры рабочей среды передаются в подоболочку вместе с переменными, которые были экспортированы в текущую оболочку Shell.

Так, команды :

CURRENTDIR=`pwd`; cd /usr/docs/otherdir;

nohup nroff doc.n > doc.out&; cd $CURRENTDIR

и

(cd /usr/docs/otherdir; nohup nroff doc.n > doc.out&)

дают один и тот же результат: файл /usr/docs/otherdir/doc.n обрабатывается nroff и вывод сохраняется в файле /usr/docs/otherdir/doc.out. (Следует заметить, что команда nroff обрабатывает текстовую информацию.) Во втором примере возврат в начальный рабочий каталог происходит автоматически. Пробелы и символы новой строки вокруг скобок допустимы, но не обязательны. При вводе командной строки с терминала Shell высветит символ приглашения в виде значения переменной PS2, в случае, если требуется закрытие скобок.

Фигурные скобки { и } могут также применяться для группирования команд. Обе эти скобки распознаются только если они находятся на месте первого (не заключенного в кавычки) слова команды. После открывающей фигурной скобки может следовать символ новой строки - в этом случае Shell выдаст приглашение для ввода дополнительной информации. В отличие от обычных скобок, для фигурных скобок не создается никаких подоболочек - заключенные в них команды просто считываются интерпретатором Shell. Фигурные скобки удобны в случае, когда вы хотите использовать (последовательный) вывод от нескольких команд в качестве ввода для одной команды

Код завершения группы команд, заключенных в скобки, равен коду завершения последней выполненной команды из этой группы.

Определение функций

В интерпретаторе Shell имеется возможность определения функций. Функции похожи на процедуры Shell за исключением того обстоятельства, что они находятся постоянно в памяти (резидентны) и поэтому выполняются самим интерпретатором. Основная форма определения функции:

name ( ) { список; }

Список может включать в себя любую команду из ранее описанных. Функции могут быть определены в одной части процедуры Shell и могут затем вызываться любое необходимое число раз. Ниже приводится пример функции с именем "getyn":

# Ответ "да" или отсутствие ответа - возвращение ненулево-

# го значения в случае "нет"

getyn ( ) {

while echo "$* (y/n)? C" >& 2

do read yn rest

case $yn in

[yY] return 0 ;;

[nN] return 1 ;;

*) echo "Please answer y or n" >&2 ;;

esac

done

}

В этом примере функция добавляет "(y/n)?" к выводу и ожидает ввода символов "Y", 'y", "N" или "n", возвращая 0 или 1. Если введены какие-либо другие символы, функция просит пользователя исправить ввод.

Функции можно использовать так же как и любые другие команды. Вызов функции getyn может быть, например, такой:

getyn "Do you wish to continue" || exit

Надо помнить, что при этом позиционные параметры $1,$2,... устанавливаются равными значениям аргументов функции. Т.к. команда exit, использованная в функции, завершает работу всей процедуры, то для возвращения обратно в процедуру требуется применять команду return.

Переопределение направления ввода/вывода и управляющие команды

Обычно Shell не разветвляет свои процессы и не создает новые подоболочки при обнаружении управляющих команд (отличных от обычных скобок). Однако, каждая команда в конвейере выполняется как отдельный процесс для того, чтобы правильно направить ввод и вывод для каждой команды. Когда переадресация ввода-вывода явно указана в команде, тогда запускается отдельный процесс для этой команды. Таким образом, когда команды if, while, until, case и for применяются в конвейере, состоящем более чем из одной команды, Shell разветвляется и подоболочка выполняет управляющую команду. При этом надо помнить:

1. Все изменения, сделанные с переменными во время выполнения управляющей команды, не сохраняются при ее завершении. Это аналогично группированию команд с помощью обычных скобок.

2. Управляющие команды при переадресации выполняются немного медленнее.

Обмен между файлами: команда (.)

Командная строка в виде :

. proc

указывает Shell считать команды из proc, не создавая нового процесса. Изменения, сделанные с переменными в proc, сохраняются после завершения команды (.). Это удобный способ инициализации сразу нескольких переменных Shell. Обычно эту команду используют для реинициализации интерпретатора Shell верхнего уровня чтением файла .profile:

. .profile

Управление прерываниями: trap

Процедуры Shell могут использовать команду trap для отключе ния сигнала (когда это требуется) или для переопределения его действия. Синтаксис команды trap следующий:

trap аргумент список_сигналов

Здесь аргумент - это текст, который интерпретируется как список команд, а список_сигналов состоит из одного или более номеров сигналов, описанных в UNIX Programmer's Reference. Наиболее важными сигналами являются:

номер

сигнал

0

Выход из Shell

1

HANGUP

2

символ INTERRUPT (DELETE или RUB OUT)

3

QUIT (Ctrl-\)

9

KILL (не может быть перехвачен или проигнорирован)

11

Изменение сегментации (не может быть пере хваченили проигнорирован)

15

Сигнал завершения работы программы

Команды, указанные в аргументе, просматриваются по меньшей мере один раз, когда Shell встречает команду trap. Из-за этого мы советуем вам заключать эти команды в одинарные, а не двойные кавычки, т.к. первые вызывают немедленную подстановку команд и переменных. Это становится важным, когда вы, например, хотите удалить временные файлы и имена этих файлов еще не определены при первом чтении команды trap интерпретатором. Следующая процедура напечатает в качестве информации для пользователя имя текущего каталога, в котором выполнялось задание, когда было получено прерывание:

trap 'echo Directory was `pwd` when interrupted` 2 3 15

for i in /bin /usr/bin /usr/gas/bin

do

cd $i

# здесь идут команды, исполняемые в каталоге $i

done

Та же самая процедура, но с двойными кавычками выполняет другие действия. Следующая команда:

trap "echo Directory was `pwd` when interrupted" 2 3 15

печатает имя каталога, из которого впервые стала выполняться процедура.

Сигнал с номером 11 никогда не может быть перехвачен, т.к. он необходим самому интерпретатору для правильного распределения памяти. Ноль интерпретируется командой trap как сигнал, образующийся при выходе из Shell. Это происходит любо при выполнении команды exit, либо при достижении конца процедуры. Если аргумент не указан, тогда при получении любого из сигналов выполняются принятые в системе по умолчанию действия. Если аргумент является пустым ("" или ''), тогда сигналы из списка сигналов игнорируются Shell'ом.

Команда trap наиболее часто используется для проверки того, что все временные файлы удалены после завершения процедуры. Предыдущий пример можно было записать так:

temp=$HOME/temp/$$

trap 'rm -f $temp; exit' 0 1 2 3 15

ls > $temp

# здесь идут команды, использующие $temp

В этом примере, как только любой из сигналов 1, 2, 3 или 15 будет получен процедурой или сама процедура будет завершаться, то будут выполнены все команды, записанные в одинарных кавычках. Команда exit здесь необходима, т.к. в противном случае Shell будет читать команды дальше после того места, где был получен сигнал. В некоторых случаях Shell продолжает чтение команд после выполнения команды trap. Приводимая ниже процедура берет каждый подкаталог из текущего каталога, переходит туда, печатает его имя и выполняет команды, вводимые с терминала до тех пор, пока не будет введен символ конца файла (Ctrl-d) или не будет получено прерывание. Конец файла вызывает возврат из команды read с ненулевым кодом завершения, тем самым прекращая цикл while и запуская цикл для следующего имени каталога. Прерывания игнорируются во время выполнения вводимых команд, но вызывают завершение, если процедура в этот момент ждет ввода:

d=`pwd`

for i in *

do if test -d $d/$i

then cd $d/$i

while echo "$i:"

trap exit 2

read x

do trap : 2

# игнорирование прерываний

eval $x

done

fi

done

Одновременно может быть указано несколько команд trap. Если различные сигналы получены одновременно, они обрабатываются по очереди в возрастающем порядке. Чтобы определить, перехват каких прерываний установлен в данный момент, введите:

trap

Важно понимать определенные моменты в том, как Shell обрабатывает команду trap. Когда Shell получает сигнал (отличный от 11), он передается во все дочерние процессы, выполняющиеся в данный момент. Когда эти (синхронные) процессы завершатся, нормально или ненормально, Shell просматривает все установленные перехваты и выполняет соответствующую команду trap. Этот процесс идет сверху вниз, за исключением перехватов, установленных во время загрузки. В этом случае возможно, что нет никаких дочерних процессов и Shell перед тем, как просматривать установленные прерывания, ждет завершения первого процесса, запущенного после получения сигнала.

Когда сигнал переопределяется в процедуре Shell, это не переопределяет сигналов для программ, вызванных из этой процедуры. Для внутренних команд Shell обычно обрабатывает прерывания после завершения команды. Исключение из этого правила сделано для команды read, чьи прерывания обрабатываются немедленно, так что команда read может быть прервана во время ожидания ввода.

Пример работы интерпретатора Shell

Ниже приводится фрагмент программы Shell, связанный с обработкой сигналов.

:

#

# Установка подпрограмм обработки сигналов

#

trap "echo

trap "echo

trap "echo

#

# Замечание: Если вы перешли в другой каталог, вы можете

# сбросить прерывание SIGQUIT с обнаружением файла "core".

# Для этого вставьте соответствующую строку после команды

# cd.

#

trap "echo

echo " Going into loop0

while true

do

cd /tmp

trap "echo

lf

cd /usr

trap "echo

lf

sleep 1

done

echo " Leaving the loop 0

exit 0

Специальные команды Shell

Существует несколько специальных команд, являющихся внутренними для Shell. Интерпретатору не требуется создавать подоболочку для выполнения этих команд и следовательно не создаются дополнительные процессы. Эти команды рекомендуется использовать везде, где это возможно, т.к. они работают быстрее и эффективнее, чем другие команды UNIX. Некоторые из специальных команд мы уже рассмотрели, т.к. они влияют на управление процессами. Это точка (.), break, continue, exit и trap. Команда set также является специальной командой. Описание остальных специальных команд приводится ниже:

: Пустая команда. Она не делает ничего и может использоваться для вставки комментариев в процедуры. Ее код завершения равен 0 (true). Все аргументы, передаваемые команде :, проверяются на синтаксические ошибки. Если выдается сообщение об ошибке, заключите эти аргументы в кавычки. Подстановка параметров выполняется как и для любых других команд.

cd arg Делает arg текущим каталогом. Если arg не является каталогом или у пользователя нет права доступа к нему, команда завершается с ненулевым кодом завершения. Указание cd без аргументов эквивалентно вводу "cd $HOME", что возвращает вас в каталог загрузки.

exec arg ... Если arg является командой, то Shell выполняет ее, не создавая подоболочки. В командной строке допускаются аргументы переадресации ввода-вывода. Если указаны только аргументы, перенаправляющие ввод-вывод, то изменяются только ввод-вывод самого интерпретатора Shell.

hash [-r] name Этой командой запоминается место в пути поиска команды, указанной в name. Опция –r указывает Shell, что старые значения можно стереть. Если никаких аргументов не указывается, то выводится информация о запомненных командах. Hits - это количество раз, которое команда вызывалась Shell.

Cost - это мера затрат, необходимых для нахождения команды. Бывают ситуации, требующие пересчета места расположения команды.

newgrp arg... Команда newgrp при выполнении создает новый интерпретатор Shell, который заменяет старый. Помните, что в новом Shell будут определены только переменные системы. Все переменные, которые раньше были помечены как экспортируемые, теперь станут обыкновенными.

pwd Печатает текущий каталог.

read var... Из стандартного ввода считывается одна строка и первое слово интерпретируется как значение первой переменной, второе - как значение второй, и т.п. Все оставшиеся считанные слова присваиваются последней переменной. Код завершения равен нулю, если не был считан символ конца файла.

readonly var... Указанные переменные помечаются как пригодные только для чтения, так что никаких последующих переприсвоений им сделать нельзя. Если никаких аргументов не указано, то выводится список всех помеченных таким образом переменных и переменных, которые экспортируются.

return n Вызывает возврат из функции с кодом завершения, равным n. Если n не указано, то код завершения равен коду завершения последней выполненной команды.

times Печатает суммарное время, использованное пользователем и процессами, запущенными из текущей оболочки.

type name Для каждого указанного имени показывает, как оно будет интерпретироваться в случае, если будет использоваться в качестве имени команды.

ulimit [ -f] n Эта команда устанавливает предел размера записываемого файла равным n блоков. Флаг -f устанавливает этот предел для файлов, записываемых дочерними процессами (считываться могут файлы любого размера). Команда, указанная без аргумента, показывает текущий установленный предел.

umask nnn Устанавливает маску для создания файлов пользователя равной nnn. Если nnn отсутствует, то печатается текущее значение маски. Эта битовая маска используется для установки прав доступа к файлам при их создании. Например, значение маски, равное восьмеричному числу 137, соответствует следующей битовой маске и правам доступа для создаваемого файла:

пользоват.

группа

другие

восьмеричн

1

3

7

бит. Маска

001

011

111

доступ

rw-

r--

---

unset name Для каждого указанного имени удаляет соответствующую переменную или функцию. Переменные PATH, PS1, PS2, MAILCHECK и IFS удалить нельзя.

wait n Shell ждет завершения всех активных дочерних процессов. Если n указано, то Shell ждет завершения только указанного процесса. Код завершения команды wait всегда равен 0, если не указан аргумент n, в противном случае он равен коду завершения дочернего процесса n.

Создание и организация процедур Shell

Процедура Shell может быть создана за два простых шага. Первый шаг - это создание обычного текстового файла. Второй - это изменение типа файла для того, чтобы он стал выполняемым и его можно было вызвать с помощью:

proc args

а не

sh proc args

Второй шаг можно не делать для процедуры, которую вы собираетесь использовать один или два раза, но для более часто используемых мы рекомендуем это делать. В качестве примера, создайте файл с именем mailall со следующим содержимым:

LETTER=$1

shift

for i in $*

do mail $i < $LETTER

done

Затем введите:

chmod +x mailall

Созданную новую команду можно теперь запускать из текущего каталога введя, например:

mailall letter joe bob

Здесь letter это имя файла, содержащего сообщение, которое вы хотите разослать, а joe и bob - это адресаты.

Если mailall создан таким способом в каталоге, указанном в переменной PATH, то пользователь может менять каталоги и вызывать mailall таким же способом.

Процедуры Shell часто используются пользователями, работающими в csh. Однако, если первым символом процедуры является # (символ комментария), то csh полагает, что данная процедура является процедурой csh и вызывает для ее выполнения /bin/csh. Всегда начинайте процедуры Shell с какого-либо другого символа, если предполагается, что ею будут пользоваться и пользователи, работающие в csh. В этом случае для ее выполнения будет вызываться стандартный интерпретатор Shell /bin/sh.

Процедуры Shell могут создаваться динамически. Процедура может генерировать файл с командами, вызывать другой интерпретатор для выполнения этого файла и затем удалять его. Альтернативный подход - это применение команды (.), чтобы заставить текущий интерпретатор прочитать команды из нового файла, использовать значения существующих переменных Shell, не создавая дополнительного процесса в подоболочке.

Многие пользователи предпочитают писать процедуры Shell, а не программы на Си или другом традиционном языке программирования. У этого есть свои причины:

1. Процедуру Shell легко создавать и работать с ней, т.к. это обычный текстовый файл.

2. У процедуры Shell нет соответствующего объектного кода, который требовалось бы создавать.

3. Процедуру Shell можно быстро создать, использовать несколько раз и удалить.

4. Поскольку процедуры Shell короткие, написаны на языке высокого уровня и содержат только исходный текст, их очень легко понимать и при необходимости исправлять.

По принятому соглашению, каталоги, которые содержат только команды и процедуры Shell, называются bin. Это имя является сокращением от binary (двоичный) и используется потому, что часто скомпилированные и выполняемые программы называют binaries, чтобы отличать их от исходных текстов программ. Большинство групп пользователей имеют свои собственные bin-каталоги для хранения общих процедур. Некоторые пользователи указывают в переменной PATH несколько таких каталогов. Мы не советуем указывать много каталогов в переменной PATH, т.к. от этого может пострадать эффективность выполнения процедур.

Флаги Shell

В Shell имеется несколько флагов, доступных для процедур.

Это:

-e Этот флаг указывает Shell на необходимость немедленного выхода, если какая-либо выполняемая команда кончается с ненулевым кодом завершения. Этот флаг полезен для процедур, составленных из простых командных строк. Его нельзя использовать в сочетании с другими условными конструкциями.

-u Этот флаг вызывает отмену значений переменных, оказавшихся ошибочными при подстановке. Этот флаг можно использовать для глобальной проверки переменных вместо условной подстановки для проверки каждой переменной.

-t Этот флаг указывает Shell'у на необходимость выхода после считывания и выполнения команд, оставшихся на текущей командной строке. Этот флаг обычно используется программами на Си, которые вызывают Shell для выполнения отдельной команды.

-n Это флаг отмены выполнения. Его используют, когда хотят проверить процедуру на наличие синтаксических ошибок, но при этом не выполнять саму процедуру. Это делается указанием "set -nv" в начале файла.

-k Этот флаг указывает Shell'у, что все аргументы в виде переменная=значение должны рассматриваться как ключевые параметры. Когда этот флаг не установлен, ключевыми параметрами считаются только аргументы, стоящие перед самой командой.

Команды поддержки и их особенности

Процедуры Shell могут использовать любую команду UNIX. Команды, описываемые в этой главе, либо особенно часто используются в процедурах, либо специально разработаны для такого использования.

Условная проверка: test

Команда test проверяет выражение, указанное в качестве аргумента, и если выражение истинно, то test заканчивается с нулевым кодом. В противном случае возвращается ненулевой код завершения. test также дает ненулевой код завершения в случае, если не указаны аргументы. Часто бывает удобно применять test в качестве первой команды в списке команд после if или while. Переменные Shell, используемые в проверяемых выражениях, должны быть заключены в двойные кавычки, если имеется вероятность, что они пустые или не определены.

В качестве альтернативного обозначения команды test можно применять квадратные скобки, например:

[ выражение ]

имеет такое же действие как:

test выражение

Помните, что пробелы перед и после выражения внутри квадратных скобок обязательны.

Ниже приведен неполный список опций, которые могут использоваться для формирования условных конструкций.

-r file Истинно, если указанный файл существует и доступен пользователю для чтения.

-w file Истинно, если указанный файл существует и доступен пользователю для записи.

-x file Истинно, если указанный файл существует и может быть выполнен пользователем.

-s file Истинно, если указанный файл существует и имеет ненулевую длину.

-d file Истинно, если указанный файл является каталогом.

-f file Истинно, если указанный файл является обычным файлом.

-z sl Истинно, если длина строки sl равна нулю.

-n sl Истинно, если длина строки sl не равна нулю.

-t fildes Истинно, если открытый файл с дескриптором fildes соответствует терминалу. Если fildes не указан, по умолчанию считается fildes=1.

s1 = s2 Истинно, если строки s1 и s2 идентичны.

s1 != s2 Истинно, если строки s1 и s2 различны.

s1 Истинно, если строка s1 ненулевая.

n1 -eq n2 Истинно, если целые числа n1 и n2 алгебраически равны.

Другие алгебраические сравнения обозначаются:

-ne (не равно), -gt (больше чем), -ge (больше или равно), -lt (меньше чем), -le (меньше или равно).

Все эти опции можно объединять со следующими операторами:

! оператор унарного отрицания

-а бинарный логический оператор AND (и)

-o бинарный логический оператор OR (или), имеет меньший приоритет, чем AND (выраж) Скобки для группирования. При отсутствии скобок проверка выражения идет слева направо.

Помните, что все опции, операторы, имена файлов и т.д. являются отдельными аргументами для test.

Команда echo

Команда echo имеет следующий синтаксис:

echo [опции] [аргументы]

echo копирует свои аргументы в стандартный вывод, после каждого кроме последнего аргумента добавляется пробел. После последнего выведенного аргумента обычно ставится символ новой строки. Вы можете использовать эту команду для выдачи приглашения для ввода, организации вывода сообщений в процедурах Shell или для добавления нескольких строк в выходной поток в середине конвейера. Другое применение echo - это проверка процесса генерации списка аргументов для других команд. Вы можете вместо команды ls использовать:

echo *

т.к. последняя работает значительно быстрее.

Опция -n указывает echo не ставить символ новой строки в конце выведенной строки. Так, следующие две команды создают приглашение для ввода и затем дают возможность вводить информацию на той же строке, где находится приглашение:

echo -n 'enter name:'

read name

Команда echo может обрабатывать некоторые ESC-последовательности, описанные в UNIX User's Reference.

Оценка арифметических выражений: expr

Команда expr осуществляет арифметические и логические действия над целыми числами и в качестве ее аргументов можно использовать шаблоны. Эта команда оценивает отдельное выражение и записывает результат в стандартный вывод. expr можно использовать внутри обратных кавычек для установки значения переменных. Ниже приводятся характерные примеры использования этой команды:

# увеличить $A

A=`expr $A + 1`

# поместить символы $1 с 3-го по последний

# в подстроку substring

substring=`expr "$1" : '..\(.*\)'`

# получить длину $1

c=`expr "$1" : '.*'`

Наиболее часто expr применяется для подсчета числа итераций в циклах.

Команды true и false

Команды true и false возвращают код завершения 0 или не 0, соответственно. Их часто используют для создания безусловных циклов, например:

while true

do

echo forever

done

В результате слово forever будет появляться на экране до тех пор, пока не будет нажато INTERRUPT.

Внутристрочный ввод документов

При обнаружении командной строки в виде:

command << eofstring

где eofstring - любая произвольная строка, Shell будет воспринимать все последующие строки как стандартный ввод для команды (command) до тех пор, пока не будет прочитана строка, содержащая только eofstring. (При добавление знака (-) к символам переадресации (<<) из каждой строки вводимого документа будут удаляться лишние пробелы и знаки табуляции.)

Shell создает временный файл, содержащий вводимый документ, и производит в нем необходимые подстановки команд и переменных и затем только пересылает его в указанную команду. При подстановке команд в командной строке выполняется поиск файлов по указанным шаблонам. Для того, чтобы отменить все подстановки, введите:

command <<\eofstring

Внутристрочный ввод документов особенно удобен для небольшого количества входных данных, поскольку гораздо удобнее помещать их сразу внутрь процедуры, а не создавать отдельный файл. Например, вы можете ввести:

cat <<-xx

Это сообщение будет выведено на терминал без лишних пробелов и знаков табуляции.

xx

Переадресация ввода-вывода с помощью дескрипторов файлов

Мы ранее уже говорили, что команда обычно направляет свой вывод в некоторый файл с дескриптором, равным 1 или 2. В языках типа Си можно связать вывод с любым дескриптором файла с помощью системной команды write (см. UNIX User's Reference). В Shell имеется свой собственный механизм создания выходного файла, связанного с конкретным дескриптором файла. Вводя :

fd1 >& fd2

где fd1 и fd2 - разрешенные дескрипторы файлов, можно направить вывод, который был связан с файлом с дескриптором fd1 в файл с дескриптором fd2. По умолчанию значения fd1 и fd2 равны 1. Если к моменту выполнения команды значение fd2 не определено, то переадресация отменяется. Наиболее часто этот механизм используется для переадресации стандартного вывода ошибок в файл стандартного вывода. Это делается так:

command 2>&1

Если вы хотите перенаправить и стандартный вывод и вывод ошибок в один и тот же файл file, введите:

command 1>file 2>&1

Порядок следования аргументов здесь важен: сначала дескриптор 1 связывается с файлом file, затем дескриптор 2 связывается с тем же файлом, т.к. он уже связан с дескриптором 1. Если порядок аргументов изменить, то вывод ошибок пойдет на терминал, а стандартный вывод пойдет в file, т.к. к моменту переадресации вывода ошибок дескриптор 1 будет связан с терминалом.

Этот механизм может быть обобщен для переадресации стандартного ввода. Если вы введете, например:

fda <&fdb

то оба дескриптора fda и fdb будут связаны с одним и тем же входным файлом. Если fda и fdb не указаны, то они принимаются равными 0. Такая переадресация ввода удобна для команд, которые используют несколько источников ввода.

Подстановка по условию

Обычно Shell заменяет все вхождения переменной $variable значением, присвоенным этой переменной variable, если оно имеется. Однако, существует определенное ограничение на разрешение условной подстановки, зависящее от того, установлена эта переменная или нет. По определению, переменная считается установленной, если ей присвоено какое-либо непустое значение. Переменной может быть присвоена нулевая строка, одним из следующих способов:

A=

bcd=""

efg=''

set '' ""

Первые три примера присваивают нулевую строку соответствующей переменной Shell. Последний пример устанавливает первый и второй позиционные параметры равными нулевой строке. Следующие условные выражения зависят от того, присвоено ли переменной непустое значение или нет. Параметр подстановки может выступать либо цифрой, либо именем переменной:

${variable:-string} Если variable установлена и ненулевая, то вместо этого выражения подставляется значение переменной $variable. В противном случае выражение заменяется значением string. Помните, что значение variable при этом не изменяется.

${variable:=string} Если variable установлена и ненулевая, то вместо этого выражения подставляется значение переменной $variable. В противном случае переменная variable устанавливаетcя равной string и затем производится подстановка вместо выражения нового значения variable. Позиционные параметры установить таким способом нельзя.

${variable:?string} Если variable установлена и ненулевая, то вместо этого выражения подставляется значение переменной $variable. В противном случае печатается сообщение в виде:

variable: string

и происходит выход из текущей оболочки. Если вы находитесь в основном Shell'е, то выхода не происходит. Если string не указана, то сообщение будет выглядеть так:

variable: parameter null or not set

${variable:+string} Если variable установлена и ненулевая, то вместо этого выражения подставляется значение string. В противном случае выражение заменяется нулевой строкой. Помните, что значение variable при этом не изменяется.

Эти выражения можно также использовать без двоеточия. В этом случае Shell только проверяет, была ли установлена переменная или нет.

Следующие два примера показывают, как пользоваться этими средствами:

1. Пример явного присвоения значения переменной PATH:

PATH=${PATH:-':/bin:/usr/bin'}

Это означает, что если переменная PATH была установлена и ненулевая, то сохраняется ее текущее значение, в противном случае она устанавливается равной ":/bin:/usr/bin".

2. Пример автоматического присвоения значения переменной HOME:

cd ${HOME:='/usr/gas'}

Если HOME установлена и ненулевая, то происходит переход в этот каталог. В противном случае HOME присваивается указанное значение и затем выполняется команда.

Флаги, устанавливаемые при вызове Shell

При вызове Shell в командной строке можно указать пять флагов. Эти флаги нельзя включить командой set.

-i Если указан этот флаг, или ввод и вывод Shell оба подключены к терминалу, то Shell является интерактивным. В таком Shell'е INTERRUPT (сигнал 2) перехватывается и игнорируется, а TERMINATE (сигнал 15) и QUIT (сигнал 3) игнорируются.

-s Если указан этот флаг или нет никаких аргументов переадресации ввода-вывода, то Shell считывает команды из стандартного ввода. Вывод Shell записывается в файл с дескриптором 2. Все остальные аргументы обозначают позиционные параметры.

-c Когда указан этот флаг, Shell считывает команды из первой строки, следующей после флага. Все остальные аргументы игнорируются.

-t Когда указан этот флаг, то считывается и выполняется только одна команда и затем происходит выход из Shell. Этот флаг рекомендуется использовать с Си-программами.

-r Если указан этот флаг, то Shell является усеченной версией Shell (см. rsh(C)).

Правила программирования в Shell

В этом разделе приводятся стратегии написания эффективных процедур Shell, которые не тратят лишние ресурсы при выполнении поставленных задач. Основной задачей является достижение желаемого результата с минимальными затратами. Акцент всегда должен ставиться на простоту, ясность и удобочитаемость, но эффективность может быть достигнута при использовании определенных стратегий программирования. Во многих случаях переорганизация процедуры повышает ее эффективность за счет уменьшения размера, и часто облегчает ее понимание. В любом случае, вы можете не оптимизировать процедуры Shell, пока они вас устраивают по быстродействию и ресурсов системы хватает для ее выполнения.

Создание процедуры Shell состоит из таких же шагов, как и написание обычных программ: написания текста, определения его размера и оптимизации только отдельных важных частей. Пользователь должен уметь свободно пользоваться командой time, которую можно использовать для определения времени выполнения как всей процедуры, так и ее частей. Мы настоятельно рекомендуем пользоваться ей, т.к. человеческая интуиция непригодна для оценки времени выполнения программ. Каждый тест должен быть выполнен несколько раз, т.к. его результаты могут зависеть от загруженности системы в данный момент.

Число генерируемых процессов

Когда выполняется большое число коротких команд, то действительное время выполнения может быть значительно увеличено за счет создания вспомогательных процессов. Процедурами, для которых требуются значительные количества таких процессов, как правило являются процедуры, в которых выполняется много циклов и которые генерируют командные последовательности, которые должны интерпретироваться другим Shell.

Если вас беспокоит эффективность выполнения, вам надо знать, какие команды являются встроенными в Shell, а какие нет. Ниже приводится список всех встроенных команд:

break case cd continue echo

eval exec exit export for

if read readonly return set

shift test times trap umask

until wait while . :

{}

Обычные скобки () также являются встроенной командой, но команда, заключенная в них, выполняется как дочерний процесс, т.е. Shell разветвляется. Все остальные команды, не указанные в списке, являются внешними (требуют fork и exec).

Пользователь должен всегда хотя бы приблизительно знать число процессов, генерируемых процедурой. Для всех приведенных ранее процедур число генерируемых (не обязательно одновременно) процессов может быть оценено по формуле:

число процессов = (k*n) + c

где k и с - это константы, а n может быть количеством аргументов процедуры, количеством строк во входном файле, количеством файлов в каталоге и т.п. Повышение эффективности обычно достигается за счет уменьшения константы k.

В качестве примера проанализируем процедуру с именем split, чей текст проведен ниже:

:

# split

trap 'rm temp$$; trap 0; exit' 0 1 2 3 15

start1=0 start2=0

b='[A-Za-z]'

cat > temp$$

# read stdin into temp file

# save original lengths of $1,$2

if test -s "$1"

then start1=`wc -l < $1`

fi

if test -s "$2"

then start2=`wc -l < $2`

fi

grep "$b" temp$$ >> $1

# lines with letters onto $1

grep -v "$b" temp$$ | grep '[0-9]' >> $2

# lines without letters onto $2

total=" `wc -l< temp$$` "

end1=" `wc -l< $1` "

end2=" `wc -l< $2` "

lost=" `expr $total - \($end1 - $start1\)\

- \($end2 - $start2\)` "

echo "$total read, $lost thrown away"

Для каждой итерации в цикле имеется одна команда expr и либо команда echo или другая команда expr. Еще одна дополнительная команда echo выполняется в конце процедуры. Если n - число строк на вводе, то количество процессов равно 2*n+1.

Некоторые разновидности процедур не следует писать на Shell'е. Так, например, если с каждым символом файла связан один или несколько процессов, хорошим решением будет перенос процедуры на язык Си. Shell-процедуры не следует использовать для посимвольного сканирования или посимвольного формирования файлов.

Количество байтов данных

Всегда полезно выполнять любые действия, которые уменьшают количество считываемых или записываемых байтов. Это может быть важно для процедур, чье основное время тратится на передачу данных между несколькими процессами, а не на создание большого числа коротких процессов. Некоторые фильтры сокращают выводной поток, другие же, как правило, увеличивают его. Например, второй из приводимых примеров работает быстрее, т.к. ввод для sort будет значительно меньше.

sort file | grep pattern

grep pattern file | sort

Поиск в каталогах

Просмотр каталогов может занимать довольно много времени, особенно в приложениях, использующих сложные структуры каталогов и длинные имена путей. Применение команды cd может вам позволить сократить длинные имена путей и тем самым уменьшить количество каталогов при поиске. В качестве упражнения проверьте следующие две команды:

ls -l /usr/bin* >/dev/null

cd /usr/bin; ls -l * >/dev/null

Вторая команда выполняется быстрее, т.к. ей требуется просмотр меньшего количество каталогов.

Порядок поиска в каталогах и переменная PATH

Переменная PATH предоставляет удобный механизм для организации процедур. Однако применять ее нужно с разумной осторожностью, иначе может произойти значительное увеличение дополнительных затрат системных ресурсов.

Процесс нахождения команды заключается в чтении содержимого каждого каталога, включенной в переменную PATH, до тех пор пока не будет найдена нужная команда. В качестве примера рассмотрим, как вызывается nroff (предположим, что команда находится в каталоге /usr/bin), когда переменной PATH присвоено значение ":/bin:/ usr/bin". Последовательность просмотра каталогов следующая:

.

/

/bin

/

/usr

/usr/bin

В нашем примере было просмотрено 6 каталогов. Если же переменной PATH присвоен длинный список путей, то количество просматриваемых каталогов может значительно возрасти.

Бездумное присвоение переменной PATH значений приводит к большим затратам времени на ненужный просмотр каталогов. Ниже приводятся четыре примера значений переменной PATH в порядке от самого плохого варианта до наилучшего:

:/usr/john/bin:/usr/localbin:/bin:/usr/bin

:/bin:/usr/john/bin:/usr/localbin:/usr/bin

:/bin:/usr/bin:/usr/john/bin:/usr/localbin

/bin::/usr/bin:/usr/john/bin:/usr/localbin

Первый вариант следует исключить. Остальные варианты приемлемы и выбор среди них определяется составом команд, содержащихся в каталогах /bin и /usr/bin.

Процедуру, которая вызывает много коротких команд, часто можно ускорить, если внутри процедуры установить переменную PATH так, чтобы просматривалось только необходимое число каталогов в оптимальном порядке.

Правила создания каталогов

Мы советуем вам не создавать каталогов больше, чем вам необходимо. Вы должны знать, что существуют несколько специальных размеров каталогов. Каталог, содержащий до 30 файлов (считая файлы . и .), умещается на одном блоке диска и может быть просмотрен очень быстро. Каталог, содержащий до 286 файлов, еще можно считать маленьким, использование каталога большего размера в качестве рабочего уже связано с определенными неудобствами. Мы не советуем делать рабочие каталоги больше, чем стандартные. Особенно важно сохранять загрузочный каталог маленьким, желательно размером в 1 блок. Помните, что каталоги, как правило, не ужимаются. Если ваш каталог содержит более 30 или 286 файлов, то поиск будет выполняться неэффективно. Более того, даже если вы удалите файлы так, что их у вас станет меньше, чем указанные пределы, операционная система все равно будет просматривать каталог неэффективно.

Примеры процедур Shell

Возможности командного языка Shell наиболее явно чувствуются при виде того, как легко можно объединить много утилит для выполнения нужных команд. В этом разделе приводятся примеры процедур. Изучая эти примеры, вы приобретете навыки программирования процедур Shell. Обратите внимание на то, что все процедуры начинаются с команды (:), а символ # открывает строку комментариев.

Для каждой процедуры надо выполнить следующие действия:

1. Создайте файл с именем процедуры и запишите ее туда.

2. Измените права доступа к файлу с помощью команды chmod.

3. Переместите файл в каталог, где хранятся команды, например, в ваш bin.

4. Убедитесь, что в переменной PATH имеется имя вашего ката лога bin.

5. Выполните указанную команду.

BINUNIQ

:

ls /bin /usr/bin | sort | uniq -d

Эта процедура определяет, какие файлы с одинаковыми именами находятся в каталогах /bin и в /usr/bin. Файлы в каталоге /bin в большинстве случаев заменяют собой одноименные файлы в каталоге /usr/bin.

COPYPAIRS

:

# Формат: copypairs file1 file2 ...

# Копирует file1 в file2, file3 в file4, ...

while test "$2" != ""

do

cp $1 $2

shift; shift

done

if test "$1" !=""

then echo "$0: odd number of arguments" >&2

fi

В этой процедуре показывается, как использовать цикл while для обработки списка позиционных параметров, определенным образом связанных друг с другом. Здесь цикл while предпочтительнее цикла for, поскольку с помощью команды shift можно производить настройку позиционных параметров на обработку связанных с ними аргументов.

COPYTO

:

# Формат: copyto dir file ...

# Копирует перечисленные файлы в каталог "dir"

# при условии, что указано не менее двух

# аргументов, что "dir" является каталогом,

# и что каждый последующий аргумент

# представляет собой файл, доступный для чтения.

if test $# -lt 2

then echo "$0: usage: copyto directory file ...">&2

elif test ! -d $1

then echo "$0: $1 is not a directory";>&2

else dir=$1; shift

for eachfile

do cp $eachfile $dir

done

fi

Эта процедура использует команду if для вывода сообщений о неправильном применении процедуры. Цикл for в конце процедуры просматривает все аргументы, кроме первого.

DISTINCT1

:

# Формат: distinct1

# Читает стандартный ввод и выводит список

# алфавитно-цифровых строк, различающихся только

# размером букв, представляя их в строчной форме

tr -cs 'A-Za-z0-9' '\012' | sort -u |\

tr 'A-Z' 'a-z' | sort | uniq -d

Эта процедура является примером процесса, создаваемого конвейером. Символ \ в конце первой строки означает, что следующая строка является продолжением. На первый взгляд представляется неясным, как эта команда работает. С принципами работы команд tr, sort и uniq можно ознакомиться в User's Reference. Команда tr преобразует все символы, кроме букв и цифр, в символы "новая строка", после чего уплотняет повторяющиеся символы. В результате каждый набор литер окажется в отдельной строке. Команда sort сортирует строки и оставляет из любой последовательности повторяющихся строк только одну строку. Следующая команда tr преобразует все буквы в строчные, делая тем самым идентификаторы, отличающиеся лишь величиной букв, одинаковыми. Вывод вновь сортируется, все дубликаты собираются вместе. Команда "uniq -d" формирует перечень строк, встречающихся в тексте более одного раза.

Процесс построения подобного конвейера основан на том факте, что каналы и файлы могут взаимозаменяться. Первая из нижеприведенных строк эквивалентна двум последующим при условии наличия достаточного дискового пространства:

cmd1 | cmd2 | cmd3

cmd1 > temp1; < temp1 cmd2 > temp2; < temp2 cmd3

rm temp[123]

Подав на стандартный ввод файл с тестовыми данными и приступив к выполнению команд слева направо, мы создаем конвейер, в котором каждая команда считывает данные из предыдущего файла и записывает результат в следующий файл. Цель - создать серию преобразований, превращающих исходные данные в требуемый результат.

DRAFT

:

# Формат: draft file(s)

# Вывод страниц на принтер.

for i in $*

do nroff -man $i | lpr

done

Пример использования явных флагов, не имеющих значений по умолчанию.

EDFIND

:

# Формат: edfind file arg

# Ищет последнее появление в файле "file" строки,

# начинающейся с подстроки "arg", после чего

# выводит 3 строки (предыдущую, найденную,

# последующую)

ed - $1 << -EOF

?^$2?

-,+p

q

EOF

Иллюстрация использования команды ed для редактирования исходных текстов, в которых Shell выполняет подстановку значений переменных.

EDLAST

:

# Формат: edlast file

# Выводит последнюю строку файла

# и удаляет ее.

ed - $1 << -\!

$p

$d

w

q

!

echo done

Иллюстрация считывания данных из файла до восклицательного знака.

FSPLIT

:

# Формат: fsplit file1 file2

# Чтение стандартного ввода и разбиение его на 3 части

# с добавлением строки, содержащей не менее одной буквы,

# к файлу file1, добавления другой строки с цифрами, но

# без букв к файлу file2, и сбрасыванием остального.

count=0 gone=0

while read next

do

count="`expr $count +1`"

case "$next" in

*[A-Za-z]*)

echo "$next" >> $1 ;;

*[0-9]*)

echo "$next" >> $2 ;;

*)

gone="`expr $gone + 1`"

esac

done

echo "$count lines read, $gone thrown away"

На каждой итерации цикла из вводного потока считывается строка и анализируется. Цикл завершается только тогда, когда команда read обнаружит конец файла. Обратите внимание на использование команды expr.

LISTFIELDS

:

grep $* | tr ":" "\012"

Эта процедура выводит в виде строк свой аргумент. Она заменяет каждый символ ":" на символ новой строки. Так, если в качестве аргумента указать:

joe newman: 13509 NE 78th St: Redmond, Wa98062

то эта процедура выведет:

joe newman

13509 NE 78th St

Redmond, Wa98062

MKFILES

:

# Формат: mkfiles pref [quantity]

# Создает файлы "quantity".

# По умолчанию - 5, как показано на следующей строке.

quantity=${2-5}

i=1

while test "$i" -le "quantity"

do

> $1$i

i="`expr $i + 1`"

done

Процедура mkfiles использует переадресацию вывода для создания файлов нулевой длины. Команда expr используется для подсчета числа итераций в цикле while.

NULL

:

# Формат: null files

# Создает каждый из указанных файлов как пустой.

for eachfile

do

>$eachfile

done

Процедура использует тот факт, что в результате переадресации вывода, если выводного файла с таким именем нет, создается пустой выводной файл.

PHONE

:

# Формат: phone initials ...

# Вывод номеров телефонов людей с

# указанными инициалами.

echo 'initsext home'

grep "$1" <<END

jfk 1234 999-2345

lbj 2234 583-2245

hst 3342 988-1010

jqa 4567 555-1234

END

Пример использования встроенной программы для поддержки не большой базы данных.

TEXTFILE

:

if test "$1"= "-s"

then

# Возвращает код завершения

shift

if test -z "`$0 $*`" # проверка возвращаемого значения

then

exit 1

else

exit 0

fi

fi

if test $# -lt 1

then echo "$0: Usage: $0 [ -s ] file ..." 1>&2

exit 0

fi

file $* | fgrep 'text' | sed 's/:.*//'

Чтобы определить, какие файлы в каталоге содержат только текстовую информацию, textfile фильтрует списки аргументов для других команд. Например, следующая командная строка напечатает все текстовые файлы в текущем каталоге:

pr `textfile *` | lpr

В процедуре используется флаг -s, отбирающий из списка текстовые файлы.

WRITEMAIL

:

# Формат: writemail message user

# Если пользователь зарегистрировался,

# сообщение выводится на терминал;

# в противном случае, посылается пользователю.

echo "$1" | { write "$2" || mail "$2" ; }

Иллюстрация использования командной группировки. Сообщение, обозначенное как "$1", передается команде write, и, в случае неудачного завершения последней, команде mail.

ИНТЕРПРЕТАТОР SHELL