Rc - оболочка Plan 9
- Том Дафф
td@plan9.bell-labs.com
-
АБСТРАКТНО Rc — это командный интерпретатор Plan 9, который обеспечивает средства, подобные к UNIX Bourne shell, но с небольшими дополнениями и менее идиосинкразтичным* синтаксисом. В настоящем документе используются многочисленные примеры для описания характеристик rc, контрасты rc и Bourne shell, модели которых многие пользователи считают подобными.
1 Введение
Rc аналогичен по духу, но различен в деталях с UNIX Bourne shell. В этом документе рассматриваются главные характеристики rc с множеством примеров, которые показывают его схожесть с Bourne shell.
2 Простые команды
В наиболее простом использовании синтаксис rc во многом схож с Bourne shell. Все нижеследующие команды в rc ведут себя весьма предсказуемо:
date cat /lib/news/build who >user.names who >>user.names wc <file echo [a-f]*.c who | wc who; date vc *.c & mk && v.out /*/bin/fb/* rm -r junk || echo rm failed!
3 Кавычки
Для работы с файлами, имена которых содержат пробелы и(или) специальные символы, нужно использовать одинарные кавычки ('):
rm 'odd file name'
В случае, если аргумент команды содержит собственную кавычку, она должна быть удвоена:
echo 'How''s your father?'
4 Метасимволы
Метасимволы в именах файлов могут заменять любой символ или группу символов, избавляя пользователя от необходимости набирать длинное имя файла или длинную цепочку команд, они позволяют ему вводить только части имен, а остальное заменять метасимволами. Аргумент без кавычек, содержащий символы * ? [, является метасимволом и используется в оболочке для сокращения имен файлов. Символ * соответствует любым последовательностям символов, ? соответствует одному символу, и [класс] соответствует любому из класса символов, перечисленных в скобках, если первый символ класса не ~, то класс дополняется. Класс также может содержать пары символов, разделенные минусом -, представляя этим соответствующий диапазон. Символ / должен явно указываться в метасимволе также как компоненты путевого имени . и ...
5 Переменные
В UNIX Bourne shell используются строковые переменные среды. В rc используются переменные, чьи значения являются списками аргументов, то есть массивами строк. Это является главным различием между rc и традиционными командными интерпретаторами UNIX. Переменным должны присваиваться значения, например:
path=(. /bin) user=td font=/lib/font/bit/pelm/ascii.9.fontКруглые скобки указывают на то, что значение переменной среды path является списком из двух строк. Переменным user и font назначены списки, содержащие по одной строке.
Чтобы определить значение какой-либо переменной, воспользуйтесь командой echo, указав в качестве аргумента имя, предваренное символом $ (который указывает интерпретатору rc, что имя переменной нужно заменить ее значением):
echo $path
Если переменная path была установлена, то эта команда эквивалентна следующей:
echo . /bin
Переменные могут быть индексными числами или списками чисел:
echo $path(2) echo $path(2 1 2)
Что эквивалентно:
echo /bin echo /bin . /bin
Пробелы, разделяющие имена переменных в скобках, могут отсутствовать; в противном случае индекс должен считаться отдельным списком, заключенным в скобки.
Нумерация строк в переменных может заменяться оператором $#. Например,
echo $#path
выведет числовое значение 2.
Следующие два присваивания не одинаковы:
empty=() null=''Первое присваивает переменной empty пустой список, второе же присваивает переменной null список, содержащий одну без символьную строку. Хотя на первый взгляд может показаться, что они одинаковые (для Bourne shell они неразличимы), но это только на первый взгляд, они ведут себя по-разному почти во всех случаях. Команда
echo $#empty
выводит 0, в свою очередь,
echo $#null
выводит 1.
Все неустановленные переменные (то есть которым не присвоены значения) имеют по умолчанию значение (). Это свойство полезно при рассмотрении значения переменной как единственной строки. С помощью оператора $" элементы строки конкатенируются в единственную строку, с пробелами, разделяющими элементы. Таким образом, если мы присваиваем:
list=(How now brown cow) string=$"list
то обе команды
echo $list
и
echo $string
будут иметь одинаковый вывод:
How now brown cow
но
echo $#list $#string
выведет
4 1
поскольку переменная $list содержит 4 элемента, а $string — 1.
6 Аргументы
Когда rc читает свой стандартный ввод из файла, то файл, в свою очередь, получает доступ к аргументам командной строки rc. Переменная $* первоначально содержит список аргументов, присвоенных ей. Имена $1, $2, .. , являются синонимами для $*(1), $*(2), .. . Кроме того, $0 — имя файла, из которого читается стандартный ввод rc.
7 Конкатенация
Rc содержит оператор конкатенации строк, символ ^, для составления аргументов из частей.
echo hully^gully
эквивалентна
echo hullygully
Допустим, что некоторая переменная i содержит имя команды, тогда
vc $i^.c vl -o $1 $i^.v
скомпилирует исходный код этой команды, оставив результат выполнения в соответствующем файле.
Операция конкатенации распространяется также и на списки. Нижеследующие строки
echo (a b c)^(1 2 3) src=(main subr io) cc $src^.c
эквивалентны
echo a1 b2 c3 cc main.c subr.c io.c
В деталях, правило звучит так: если оба операнда ^ — списки одного и того же не равного нулю количества строк, то они конкатенируются попарно. Если же один из операндов — единственная строка, то она конкатенируется с каждым членом другого операнда. Любая другая комбинация операндов является ошибочной.
8 Свободные символы
Чтобы сделать синтаксис rc более схожим с Bourne shell, символы должны находится только в строго определенных местах. Например,
cc -$flags $stems.c
эквивалентна
cc -^$flags $stems^.c
В общих чертах, rc вставляет символ ^ между двумя аргументами, которые не разделяются интервалом. (Вдумайтесь в следующее предложение :) — прим. пер.) Всякий раз, когда один из символов $'` следует за словом в кавычках или без них, или слово без кавычек следует за словом в кавычках, без интервальных пробелов или символов табуляции, то подразумевающийся символ ^ вставляется между ними двумя. Если слово без кавычек следует за символом $ и содержит не печатаемые символы, тогда перед первым таким символом вставляется * или ^.
9 Командная подстановка
Часто бывает полезно использовать подстановку результатов выполнения команды как аргумент командной строки. Rc допускает команды, заключенные в скобки и следующие за левой кавычкой `{...} везде, где требуется аргумент. Команда выполняется и ее стандартный вывод захватывается, символы, сохраненные в переменном ifs используются для разделения вывода на аргументы. К примеру,
cat `{ls -tr|sed 10q}
выводит десять имен самых старых файлов из текущего каталога на стандартный вывод.
10 Конвейерная обработка
Нормальная конвейерная обработка является общим средством для решения многих задач. Очень редко используются не линейные конвейеры. Синтаксис rc поддерживает некоторые типы не линейных, но дерево-подобных конвейеров. К примеру, строка
cmp <{old} <{new}
является регрессией-тестом для новой версии команды. Символ < или > следует перед именем команды и заставляет ее запускаться с использованием, соответственно, ввода или вывода аргументов в скобках, указанных в канале.
11 Код завершения процесса
Когда команда завершается, она может возвратить числовой код своего состояния программе, которая ее вызвала (запустила). По коду состояния вызывающий процесс определяет успешность завершения выполнения команды. В Plan 9 состояние процесса является символьной строкой, описывающей условие ошибки. При нормальном завершении команды эта строка пуста.
Rc сохраняет код завершения команды в переменном $status. Для простой команды, значение $status — такое же, как описано выше. Для конвейера, $status является набором конкатенаций состояний конвейерных компонент с разделяющими их символами |.
Rc имеет несколько типов управления потоками, многие из которых обусловлены состоянием последней завершенной команды. Любая переменная $status, содержащая только нули и символы | имеет логическое (булевое) значение true, любые другие символы — false.
12 Командное группирование
Последовательность команд, заключенная в скобки {}, может использоваться везде, где требуется команде. К примеру, выполнение строки:
{sleep 3600;echo 'Time''s up!'}&
заставляет интерпретатор перейти в состояние ожидания на один час в фоновом режиме, затем выводится сообщение. Без скобок:
sleep 3600;echo 'Time''s up!'&
на один час блокирует терминал, затем в фоновом режиме выводит сообщение.
13 Управляющая конструкция for
Команда может быть выполнена один раз для каждого члена списка, примерно вот так:
for(i in printf scanf putchar) look $i /usr/td/lib/dw.dat
Эта команда просматривает каждое из слов printf, scanf и putchar в данном файле. Общая форма
for(имя in список) команда
или
for(имя) команда
В первом случае команда выполняется один раз для каждого члена из списка вместе с членом, присвоенным переменной имя. Если часть ``in список'' отсутствует, то по умолчанию принимается как ``in $*''.
14 Условная конструкция if
Rc поддерживает общий if-оператор. Например:
for(i in *.c) if(cpp $i >/tmp/$i) vc /tmp/$i
запускает компилятор C для каждой исходной программы, которую программа cpp обработала без ошибок. Оператор `if not' обеспечивается двойным разветвленным условием:
for(i){ if(test -f /tmp/$i) echo $i already in /tmp if not cp $i /tmp }
Здесь выполняется цикл над каждым файлом из $*, то есть в каталог /tmp копируются те файлы, которые уже прошли цикл, а сообщение выводится для тех, которые еще в цикле.
15 Управляющая конструкция while
В rc работу оператора while можно рассмотреть на примере:
while(newer subr.v subr.c) sleep 5
Здесь проверяется завершение работы компилятора C, при этом происходит ожидание до тех пор, пока файл subr.v не станет новей, чем subr.c.
Если управляющая конструкция команды — пуста, то работа цикла не завершается. Таким образом,
while() echo y
эмулирует UNIX команду yes.
16 Управляющая конструкция switch
Для сравнения строк в rc предусмотрен оператор switch. Общая форма
switch(выражение){ case шаблон ... команды case шаблон ... команды ... }
Оператор switch последовательно сравнивает выражение с шаблонами каждого case оператора очереди. Шаблоны могут содержать имена файлов, так что они не должны содержать явно указанные символы /, . и ...
Если шаблон соответствует выражению, то выполняются команды, следующие за этим оператором до следующего case (или конца оператора switch), после этого происходит завершение работы всего switсh оператора. К примеру,
switch($#*){ case 1 cat >>$1 case 2 cat >>$2 <$1 case * echo 'Usage: append [from] to' }
команда добавления (append). Вызванная с одним аргументом, — именем файла, она добавляет свой стандартный вывод в указанный файл. С двумя аргументами — содержимое первого файла добавляется во второй файл. Любая другая комбинация выводит сообщение об ошибке.
Встроенная команда ~ также соответствует шаблонам и часто она более краткая, чем switch. Ее аргументами являются строка и список шаблонов. Она устанавливает значение true переменной $status если хоть один из шаблонов соответствует строке. Следующий пример обрабатывает аргументы выбора для системной команды man(1):
opt=() while(~ $1 -* [1-9] 10){ switch($1){ case [1-9] 10 sec=$1 secn=$1 case -f c=f s=f case -[qwnt] cmd=$1 case -T* T=$1 case -* opt=($opt $1) } shift }
17 Функции
Функции rc описываются в таком виде:
fn название { команды }
Всякий раз, когда встречается имя команды, соответствующее названию функции, остатком аргумента назначается $* и rc выполняет соответствующие команды функции. Значение $* восстанавливается после завершения команд функции. Например,
fn g { grep $1 *.[hcyl] }
определяет некий шаблон и поиск соответствий этого шаблона в исходных файлах текущего каталога.
Функция удаляется посредством выполнения команды
fn название
без тела функции.
18 Выполнение команд
В Plan 9 функция, определенная посредством fn, имеет больший приоритет выполнения, чем простая, не встроенная команда. (О встроенных командах читайте ниже пункт 19.) То есть, если имя команды является названием функции, то выполняется соответствующая функция. Если же имя является встроенной командой, то она выполняется непосредственно в rc. В противном случае, в каталогах, указанных в переменной $path производится поиск этого исполняемого файла. Расширенное использование переменной $path не приветствуется в Plan 9. Взамен, используются каталоги по умолчанию (. /bin).
19 Встроенные команды
Несколько команд, выполняемых непосредственно в rc:
- . [-i] файл ...
- Выполняет команды из файла. Переменная $* устанавливается для длительности напоминания о списке аргументов, следующих за файлом. $path используется для поиска файла. Опция -i указывает диалоговый ввод — приглашение (значение переменной $prompt) выводится перед чтением каждой команды.
- builtin команда ...
- Выполняет команду как стандартную, за исключением того, что любая
функция, имеющая эквивалентное имя, будет проигнорирована. К примеру, функция
-
fn cd{ builtin cd $* && pwd }
- cd [каталог]
- Производит смену текущего каталога на указанный в аргументе. Аргумент по умолчанию — значение из переменной $home. $cdpath является последним аргументом поиска каталога.
- eval [аргументы ...]
- Все аргументы конкатенируются (разделенные пробелами) в строку, читаются
как стандартный ввод для rc, и выполняются. Например,
-
x='$y' y=Doody eval echo Howdy, $x
-
Howdy, Doody
-
echo Howdy, $y
- exec [команда ...]
- Rc заменяет себя данной командой. Это схоже с goto, rc не ожидает завершения команды и не возвращается для чтения других команд.
- exit [состояние]
- Rc немедленно производит выход с указанным состоянием. Если состояние не указано, то используется значение переменной $status.
- flag f [+-]
- Эта команда выполняет манипулирование и тестирование флагов командной строки
(описанных ниже).
-
flag f +
-
flag f -
-
flag f
-
if(flag x) flag v +
- rfork [nNeEsfF]
- Использует вход системы rfork Plan 9 для установки rc в новую
группу процессов со следующими атрибутами:
Флаг Название Функция n RFNAMEG Создать копию родительского пространства имен N RFCNAMEG Начать с нового, пустого пространства имен e RFENVG Создать копию родительской среды E RFCENVG Начать с новой, пустой среды s RFNOTEG Создать новую группу примечаний f RFFDG Создать копию родительского пространства файловых дескрипторов F RFCFDG Создать новое, пустое пространство файловых дескрипторов - shift [n]
- Удаляет первые n (по умолчанию 1) элементов из $*.
- wait [pid]
- Ожидает процесс, идентификатор которого указан, для выхода. Если идентификатор процесса pid не указан, то ожидают все процессы.
- whatis имя ...
- Выводит значение каждого имени в пригодной форме для ввода rc.
Вывод является назначением переменной, определение функции, вызов builtin
для встроенной команды, или путевое имя исполняемой программы. Например, строка
-
whatis path g cd who
-
path=(. /bin) fn g {gre -e $1 *.[hycl]} builtin cd /bin/who
- ~ предмет шаблон ...
- Предмет соответствует каждому шаблону из очереди. Если есть соответствие,
$status устанавливается как true. В противном случае, он устанавливается
как 'no match'. Шаблоны могут соответствовать именам файлов. Шаблоны
не могут соответствовать именам файлов перед выполнением команды ~,
так что они не должны заключаться в кавычки, если же используются специальные
символы *, [ или ?, тогда кавычки необходимы. К
примеру,
-
~ $1 ?
-
~ $1 '?'
20 Представление переадресации ввода-вывода
Rc допускает переадресацию файловых дескрипторов 0 и 1 (стандартные ввод и вывод), заключая файловый дескриптор в квадратные скобки [ ] после символов < или >. Например,
vc junk.c >[2]junk.diag
сохраняет диагностику компилятора из стандартного потока ошибок в файле junk.diag.
Файловый дескриптор может заменяться копией уже открытого файла, в значении dup(2), например,
vc junk.c >[2=1]
Заменяет файловый дескриптор 2 файловым дескриптором 1. Это более полезно при использовании вместе с переадресациями, вот так:
vc junk.c >junk.out >[2=1]
Переадресации оцениваются слева-направо, так что эта строка переадресует файловый дескриптор 1 на файл junk.out, затем указывает файловому дескриптору этот же файл. Команда
vc junk.c >[2=1] >junk.out
переадресует файловый дескриптор 2 на копию файлового дескриптора 1 (возможно, терминал), а затем адресует файловый дескриптор 1 на файл. В первом случае стандартный и диагностический выводы попадают в junk.out. Во втором, диагностический вывод появляется на терминале, а стандартный вывод направляется в файл.
Файловые дескрипторы могут закрываться с использованием дублированной нотации с пустой правой стороной. Например,
vc junk.c >[2=]
отвергает сообщения диагностики из компиляции.
Произвольные файловые дескрипторы могут посылаться через канал, например,
vc junk.c |[2] grep -v '^$'
Удаляет пустые строки из вывода ошибок компилятора C. Имейте в виду, что вывод grep все еще появляется в файловом дескрипторе 1.
Если вы хотите подключить сторону вывода канала к некоторому файловому дескриптору кроме нуля, команда
cmd1 |[5=19] cmd2
создает конвейер с файловым дескриптором 5 (cmd1), соединенным посредством канала с файловым дескриптором 19 (cmd2).
21 Документ здесь
Оболочка rc поддерживает конструкцию, называемую ``документ здесь'', она удобна если нужно прочесть какие-либо данные со стандартного ввода и поместить их прямо в сценарий, но создавать файл для приема нежелательно. Она схожа с версией команды tel.
for(i) grep $i <<! ... tor 2T-402 2912 kevin 2C-514 2842 bill 2C-562 7214 ... !
Конструкция ``документ здесь'' использует оператор <<, после которого следует маркер EOF (в данном примере это !). Строки, следующие за командой, вплоть до строки, содержащей только маркер EOF, сохраняются во временном файле, который подключается к командному стандартному вводу при запуске.
Rc производит подстановку переменных в документах здесь. Следующая команда:
ed $3 <<EOF g/$1/s//$2/g w EOF
заменяет все случаи $1 на $2 в файле $3. Чтобы вставить символ $ в документ здесь, введите $$. Если имя переменной сопровождается символом ^, он удаляется.
Подстановка переменных может полностью подавляться закрытием маркера EOF (следующего за оператором <<) в кавычки, как здесь <<'EOF'.
Документы здесь могут обеспечиваться в файловых дескрипторах, кроме 0, вот так
cmd <<[4]End ... End
Если здесь документ появляется в пределах сложного блока, содержимое документа должно следовать после всего блока:
for(i in $*){ mail $i <<EOF } words to live by EOF
22 Кэширование сообщений
Сценарии rc нормально завершают свою работу, если они получают прерывание от терминала. Функция с именем UNIX сигнала, в верхнем регистре, определяется в обычном виде, но посылается когда rc получает соответствующее сообщение. Страница руководства программиста, посвященная notify(2), описывает сообщения в деталях. Вот некоторые из этих сообщений:
- sighup
- Сообщение имело название `hangup', Plan 9 посылает его, когда терминал отключается от rc.
- sigint
- Сообщение имело название `interrupt', обычно посылается, когда символ прерывания (ASCII DEL) набран в терминале.
- sigterm
- Сообщение имело название `kill', нормально посылается командой kill(1).
- sigexit
- Искусственное сообщение, когда rc собирается завершить роботу.
Например,
fn sigint{ rm /tmp/junk exit }
перехватывает прерывание с клавиатуры, которое удаляет временный файл перед выходом.
Сообщение будет проигнорировано, если программа сообщения заключена в скобки {}. Сигналы возвращаются в исходное состояние, если их управляющие определения удалены.
23 Среда
Среда — это список пар имя-значение, который делает возможным запуск исполняемых файлов. В Plan 9 среда сохраняется в файловой системе под названием #e, которая монтируется в каталоге /env. Значение каждой переменной сохраняется в отдельном файле с компонентами завершенными нулевыми байтами. (Файловая система полностью поддерживается в ядре ОС, так что ни о каком дисковом или сетевом доступе и речи быть не может.) Содержимое каталога /env разделено между базовыми над-процессовыми группами — когда создается новая группа процессов, она эффективно подключается в /env новой файловой системы, проинициализированную с копией старой группы процессов. Последствием такой организации является то, что команды могут изменять среду данных и просматривать изменения, отраженные в rc.
В среде также появляются функции, к их именам добавляется префикс fn#, как здесь /env/fn#roff.
24 Локальные переменные
Часто бывает необходимо установить длительность одной команды. Следующее за командой назначение делает такой эффект. Например,
a=global a=local echo $a echo $a
выводит
local global
Это также работает и в составных командах, подобно
f=/fairly/long/file/name { { wc $f; spell $f; diff $f.old $f } | pr -h 'Facts about '$f | lp -dfn }
25 Примеры cd, pwd
Ниже приводится пара функций, которые обеспечивают расширенные версии стандартных команд cd и pwd. (Спасибо за них Робу Пайку.)
ps1='% ' # приглашение по умолчанию tab=' ' # символ табуляции fn cd{ builtin cd $1 && switch($#*){ case 0 dir=$home prompt=($ps1 $tab) case * switch($1) case /* dir=$1 prompt=(`{basename `{pwd}}^$ps1 $tab) case */* ..* dir=() prompt=(`{basename `{pwd}}^$ps1 $tab) case * dir=() prompt=($1^$ps1 $tab) } } } fn pwd{ if(~ $#dir 0) dir=`{/bin/pwd} echo $dir }
Функция pwd является стандартной версией команды pwd, которая кэширует свое значение в переменную $dir, поскольку истинная pwd может медленно выполняться. (Последние версии Plan 9 содержат очень быстрые реализации pwd, уменьшая преимущества этой pwd функции.)
Функция cd вызывает встроенную команду cd и проверяет успешность ее выполнения. Если выполнение прошло успешно, то устанавливает переменные $dir и $prompt. При этом приглашение будет содержать последнюю часть текущего каталога (кроме домашнего каталога, где она будет равняться нулю), и $dir будет восстановлена или в корректную величину или в (), так что функция pwd будет работать вполне адекватно.
26 Примеры man
Команда man выводит страницы из руководства программиста. Она может быть вызвана таким образом:
man 2 sinh man rc man -t cat
В первом случае, выводится страница руководства из второй секции, посвященная sinh. Во втором случае, страница руководства для rc. Так как руководство разделено на секции, поиск производится во всех секциях, в случае с rc, страница найдена в первой секции. В третьем случае, страница для cat является автонабором (опция -t).
cd /sys/man || { echo $0: No manual! >[1=2] exit 1 } NT=n # nroff по умолчанию s='*' # секция, по умолчанию проверяет все for(i) switch($i){ case -t NT=t case -n NT=n case -* echo Usage: $0 '[-nt] [section] page ...' >[1=2] exit 1 case [1-9] 10 s=$i case * eval 'pages='$s/$i for(page in $pages){ if(test -f $page) $NT^roff -man $page if not echo $0: $i not found >[1=2] } }
Команда eval здесь используется для создания списка возможных man-страниц. Без eval, все соответствия *, сохраненные в переменной $s, не будут соответствовать именам файлов, не заключенных в кавычки. Eval заставляет свои аргументы обрабатываться синтаксическим анализатором и интерпретатором rc, эффективно задерживая оценку *, до назначения переменной $pages.
27 Примеры holmdel
Нижеследующий сценарий rc запускает обманчиво простую игру holmdel, в которой игроки угадывают расположение Bell Labs, победитель первым указывается в Holmdel.
t=/tmp/holmdel$pid fn read{ $1=`{awk '{print;exit}'} } ifs=' ' # просто новая строка fn sigexit sigint sigquit sighup{ rm -f $t exit } cat <<'!' >$t Allentown Atlanta Cedar Crest Chester Columbus Elmhurst Fullerton Holmdel Indian Hill Merrimack Valley Morristown Neptune Piscataway Reading Short Hills South Plainfield Summit Whippany West Long Branch ! while(){ lab=`{fortune $t} echo $lab if(~ $lab Holmdel){ echo You lose. exit } while(read lab; ! grep -i -s $lab $t) echo No such location. if(~ $lab [hH]olmdel){ echo You win. exit } }
Этот сценарий трудно описать в деталях (может быть потому что он такой примитивный).
Переменная $t является аббревиатурой имени временного файла. Включая переменную $pid, проинициализированную rc в свой идентификатор процесса, временные файлы делают так, чтобы их имена не вступали в противоречия, в случаях, когда запущен более чем один пример сценария за раз.
Аргументом функции read является имя переменной, в которую собрана строка из стандартного ввода для чтения. Значение $ifs — новая строка. Поскольку ввод read не разделяется пробелами, завершающая строка удаляется.
Конструктор устанавливается для кэширования сигналов sigint, sigquit, sighup, и искусственного sigexit. Он просто удаляет временные файлы и завершает работу сценария.
Временный файл, содержащий список расположений Bell Labs, инициализируется конструкцией документ здесь и начинает основной цикл.
Сначала программа угадывает расположение (занося его в переменную $lab), используя шуточную программу fortune для выбора произвольной строки из списка расположений. Она выводит расположение, и если это Holmdel, то выводит сообщение о проигрыше и завершает работу.
Затем она использует функцию read, чтобы получить строки из стандартного ввода, проверяет правильность ввода, до тех пор пока не получит правильное расположение. Часть условия while может быть заменена составной командой. Проверке подлежит лишь выходное состояние последней команды из последовательности.
Опять, если получен результат Holmdel — выводится сообщение о проигрыше и программа завершает работу. В противном случае происходит переход на верх цикла.
28 Принципы конструкции
Rc многое перенял от оболочки Steve Bourne /bin/sh. Любой преемник Bourne shell обязан поддаваться сравнениям. Я, как автор оболочки rc, обычно опуская несущественные характеристики предшественницы, пытался устранить наиболее известные ее недостатки и по возможности упростить вещи. Я вводил новые идеи только в крайних случаях. Очевидным является то, что я хорошо повозился с синтаксисом Bourne.
Самым важным принципом конструкции оболочки rc является то, что она не является препроцессором. Ввод никогда не считывается более одного раза кодом лексического и синтаксического анализа (кроме, конечно же, команды eval, чье основное raison d'être нарушать принятые правила).
Сценарии Bourne shell часто пишутся для запуска с аргументами, содержащими пробелы. Они разделяются на многочисленные аргументы с использованием IFS, часто несвоевременно. В rc значения переменных, включая аргументы командной строки, не перечитываются повторно при подстановке в команду. Аргументы, возможно, были введены родительским процессом и не должны повторно считываться.
Почему же тогда Bourne shell повторно считывает команды после подстановки из переменных? Оболочка нуждается в сохранении списков аргументов в переменных, чьими значениями являются символьные строки. Если же мы устранили повторное считывание, мы должны изменить тип переменных, чтобы они могли принимать списки строк.
Это приводит к некоторым концептуальным осложнениям. Нам требуется нотация для списков слов. Всего существует два вида конкатенации: для строк $a^$b, и для списков ($a $b). Как раз отличия () и '' наиболее запутывают новичков, хотя различия во многом спорны, так нулевой аргумент не эквивалентен отсутствию аргумента.
Bourne также проводит повторное считывание ввода при подстановке команд. Дело в том, что текст, заключенный в обратные кавычки не является строкой. При правильном использовании он должен быть разбит на заключенные в кавычки команды, но это делает трудным оперирование подстановками вложенных команд, подобно этой:
size=`wc -l \`ls -t|sed 1q\``
Использование внутренних обратных кавычек должно быть ограничено во избежание завершения внешней команды. Может возникнуть ситуация значительно хуже вышеуказанного примера. В rc используется унарный оператор обратной кавычки, чей аргумент является командой, подобно этой:
size=`{wc -l `{ls -t|sed 1q}}
При этом не требуются никакие переходы и вся строка обрабатывается за один проход.
По аналогичным причинам rc определяет управляющие сигналы как функции, вместо ассоциаций строки с каждым сигналом, как это делает Bourne shell, с сопровождающейся возможностью получения сообщений о синтаксических ошибках в ответ на ввод символа прерывания. Так как rc анализирует ввод при наборе, то сообщает об ошибках, когда вы их делаете.
Для всей этой проблемы мы увеличиваем надежность семантических упрощений. Нет необходимости в различиях между $* и $@. Нет необходимости ни в четырех типах кавычек, ни чрезвычайно сложных правилах управления ими. Вы используете кавычки в rc когда хотите, чтобы синтаксический символ появлялся в аргументе, или аргумент был пустой строкой, и никаких других вариантов. Больше не используется IFS, кроме случаев, когда она необходима: преобразование вывода команды в список аргументов при командных подстановках.
При этом избегается важная дыра в безопасности UNIX. В UNIX, для выполнения команд, функции system и popen вызывают интерпретатор /bin/sh. Невозможно использовать любую из этих программ с гарантией, что определенная команда будет обязательно выполнена. Это может быть разрешимо, если эти действия происходят в программе с установленным пользовательским идентификатором. Проблема в том, что IFS используется для разделения команды на слова, так что взломщик может установить значение переменной IFS=/ в своей среде и оставить троянского коня под именем usr или bin в текущем каталоге перед запуском привилегированной программы. Rc не допускает этого, потому что ни в каком случае не производит повторного считывания ввода.
Большинство других различий между rc и Bourne shell не так значительны. Я устранил специфические формы переменной подстановки Bourne shell, подобно
echo ${a=b} ${c-d} ${e?error}
потому что они мало используются, излишни и легко могут быть выражены в более понятных терминах. Мною были удалены функции export, readonly, break, continue, read, return, set, times и unset поскольку они мне кажутся лишними и полезными лишь в очень редких случаях.
Синтаксис Bourne shell происходит от языка программирования Algol 68, в свою очередь синтаксис rc основан на C или Awk. Мне кажется что, к примеру, строка
if(test -f junk) rm junk
имеет лучший синтаксис, чем
if test -f junk; then rm junk; fi
поскольку она менее приведена в беспорядок ключевыми словами, синтаксис rc избегает использования точек с запятой, которые в оболочке Bourne требуется ставить в довольно странных местах.
Но, все-таки, один кусочек из крупномасштабного синтаксиса Bourne организован бесспорно лучше, чем в rc — это конструкция if с вкладкой else. If в rc не содержит завершающей fi-подобной скобки. В результате синтаксический анализатор не может ни сообщить, ни объяснить вкладку else без просмотра верхней части конструкции своего ввода. Проблема в том, что например, после чтения строки
if(test -f junk) echo junk found
в интерактивном режиме, rc не может решить: выполнить всю строку немедленно и вывести $prompt(1), или же вывести $prompt(2) и ожидать введения else. В Bourne shell эта проблема не наблюдается, поскольку команда if должна заканчиваться словом fi, независимо от того, содержит она else или нет.
По общему признанию, довольно хилым решением проблемы является объявление вкладки else как отдельного утверждения с семантическим условием, оно должно следовать непосредственно за if и вызывать чаще if not чем else как напоминание, что будет происходить что-то странное. Единственно заметное последствие этого, в том, что в конструкции требуется использование скобок
for(i){ if(test -f $i) echo $i found if not echo $i not found }
этим rc решает проблему ``зависаний else'' неоднозначно в оппозиции к многим ожиданиям людей.
В четырех последних изданиях руководства программиста системы UNIX, грамматика оболочки Bourne, описанная в man-странице, не допускает использования команды who|wc. Это несомненно оплошность, но под этим можно понимать и нечто большее: никто точно не может объяснить, что такое грамматика Bourne shell. Даже исследования исходного кода оболочки не дают никаких результатов. Синтаксический анализатор имеет рекурсивное происхождение, но соответствия программ синтаксическим категориям содержат флаговый аргумент, который тонко изменяет их зависимость действий в контексте. Синтаксический анализатор rc осуществлен c использованием yacc, так что я точно могу сказать что такое грамматика.
29 Благодарности
Роб Пайк, Говард Трики и другие пользователи Plan 9 были настойчивыми, непрерывными источники хороших идей и критики. Некоторые примеры настоящего документа была заняты из [Bourne], также как и наилучшие характеристики rc.
30 Литература
S. R. Bourne, UNIX Time-Sharing System: The UNIX Shell, Bell System Technical Journal, Volume 57 number 6, July-August 1978
31 Сноски
(*Идиосинкразия — повышенная чувствительность к определенным веществам или взаимодействиям; часто возникает после первого контакта с раздражителем. Проявления — отек кожи, крапивница и т.п. — Прим пер.:)
Copyright © 2000 Lucent
Technologies Inc. All rights reserved.
Copyright © 2003 Перевод Андрей
С. Кухар.