Введение
Общий механизм выполнения команд
Файлы, ассоциированные с процессом
Конвейеры
Переменные и аргументы shell-процедур
Служебные переменные shell`а
Подстановка результатов выполнения команд
Подстановка результатов выполнения команд
Генерация имен файлов
Экранирование
Переназначение ввода/вывода
Окружение процессов
Внутренние команды shell`а
Резюме

В статье, носящей учебный характер, приводятся основные сведения о стандартном командном интерпретаторе UNIX System V. Рассматриваются типичные приемы программирования на языке shell. Приводятся примеры shell-процедур.

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

Введение

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

Обычно в ОС UNIX доступны несколько интерпретаторов. Наиболее распространены Bourne-shell (или просто shell, автор - Стив Баурн, AT&T), С-shell (Билл Джой, Университет Беркли), Korn-shell (Дэвид Корн, AT&T). В идейном плане эти интерпретаторы близки, и мы остановимся на стандартном shell`е (программа /bin/sh) как наиболее простом и в то же время важным для системного администрирования.

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

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

Общий механизм выполнения команд

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

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

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

К сожалению, красивую идею выполнения всех команд в рамках порожденных процессов не удастся провести в жизнь на 100%. Некоторые действия, в основном касающиеся изменения внутреннего состояния, приходится брать на себя самому shell`у. Например, если доверить порожденному процессу смену текущего каталога, то процесс его, конечно, сменит (себе), после чего завершится с чувством исполненного долга, а shell останется там, где и был. В результате с точки зрения shell`а команды делятся на внешние (которые он только запускает) и внутренние, которые ему приходится выполнять самому.

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

type имя

(type - внутренняя команда shell`а). shell отреагирует одним из трех способов (строки, вводимы пользователем, начинаются с приглашения - $):

$ type cd
cd is a shell builtin
$ type sh
sh is /bin/sh
$ type qq
qq not found

(cd - внутренняя команда, sh - программа, извлекаемая и каталога /bin, файл с именем qq найти не удалось).

Файлы, ассоциированные с процессом

Как правило, с каждым процессом в ОС UNIX ассоциировано по крайней мере три файла:

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

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

Конвейеры

Главная сила UNIX`а проистекает не из мощи отдельных утилит, а из возможностей их сопряжения. Конвейеры - один из самых красивых и продуктивных механизмов подобного сопряжения.

Конвейер - это последовательность команд, разделенных знаком |. При этом стандартный вывод всех команд, кроме последней, направляется на стандартный ввод следующей команды конвейера. Каждая команда выполняется как самостоятельный процесс; shell ожидает их завершения.

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

ls -a/ | wc -l

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

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

ls -al | grep "Oct"

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

Можно выстроить и трехступенчатый конвейер, если требуется подсчитать число файлов, модифицированных в октябре:

ls -al | grep "Oct" | wc -l

Здесь команду grep с еще большим правом можно назвать фильтром.

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

ls -al|grep "Oct"|tree /tmp/tmpinf|wc -l

В результате выполнения этого конвейера на экране появится число нужных файлов, а в файле /tmp/tmpinf - информация о них.

Переменные и аргументы shell-процедур

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

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

Все значения в языке shell трактуются как текстовые.

Обычно в языках программирования ясно из контекста, где имеется в виду имя переменной, а где значение. Так, в левой части оператора присваивания обычно используется имя, в правой - значение. В языке shell это не так. Для обозначения перехода от имени переменной к значению используется явная операция $. Если в команде встречается конструкция

$имя

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

${имя}

с тем же смыслом, если нужно отделить имя от последующего текста.

Рассмотрим пример. После выполнения команд

a=value_of_variable
b=1+2
echo a=$a
echo b=$b

[команда echo (эхо) выдает на стандартный вывод свои аргументы] на экране появится:

a=value_of_variable
b=1+2

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

$цифра

$0 есть имя интерпретируемой shell-процедуры.

Рассмотрим shell-процедуру, которая выдает на стандартный вывод свое имя и значения первых трех аргументов:

echo Имя команды: $0
echo Имя команды: $1
echo Имя команды: $2
echo Имя команды: $3

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

three_args arg1 . - arg4

на экране появится:

Имя команды: three_args
Значение первого аргумента: arg1
Значение второго аргумента: .
Значение третьего аргумента: -

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

Команда

three_args arg1 --

выдаст:

Имя команды: three_args
Значение первого аргумента: arg1
начение второго аргумента: --
Значение третьего аргумента: 

Значение третьего аргумента пусто.

Прежде чем начнется выполнение командной строки, то есть будет вызвана заданная в ней команда, строка обрабатывается shell`ом, который выполняет в ней некоторые подстановки. С одной из таких подстановок - значения переменной вместо конструкции $имя - мы уже познакомились. Язык shell содержит ряд аналогичных конструкций. Рассмотрим одну из них. Там, где в командной строке встречается запись вида

${имя:-слово}

вместо нее подставляется значение переменной с указанным именем, если это значение непусто; в противном случае подставляется в слово. Например, если в shell-процедуре встретилось присваивание

initdir=${1:-/}

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

Служебные переменные shell`а

Значения некоторых переменных устанавливаются самим shell`ом. Перечислим наиболее употребительные их этих переменных и опишем их предназначение. # Количество фактических аргументов (десятичное). $ Идентификатор процесса, в рамках которого выполняется shell. *, @ Совокупность всех фактических аргументов (начиная с $1), разделенных пробелами. Напомним: чтобы получить значения этих переменных, перед ними нужно поставить знак $.

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

Несколько усложним процедуру three_args, чтобы продемонстрировать только что описанные возможности.

echo Идентификатор текущего процесса: $$
echo Имя команды: $0
echo Число фактических аргументов: $#
echo Совокупность всех аргументов: $@
echo Значение первого аргумента: $1
echo Значение второго аргумента: $2
echo Значение третьего аргумента: $3

Если теперь выполнить командную строку

tree_args arg1 . - arg4

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

Идентификатор текущего процесса: 168
Имя команды: tree_args
Число фактических аргументов: 4
Совокупность всех аргументов: arg1 . - arg4
Значение первого аргумента: agrl
Значение второго аргумента: .
Значение третьего аргумента: -

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

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

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

i=`expr $i + 1`

значение переменной i увеличится на 1.

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

Язык shell, как и всякий универсальный язык программирования, представляет ряд конструкций для управления последовательностью действий. Упомянем три из них:

for имя in слово ...
do
команда
   .  .  .
else
команда
   .  .  .
fi

В качестве условия может фигурировать любая команда, однако в данном контексте важен лишь код ее завершения. Если он нулевой, условие считается истинным; в таком случае выполняется then-часть, иначе - else часть. Как и в других языках, else-часть может отсутствовать.

Возможно также употребление связки elif (сокращение для else if). Отметим, что чаще всего для проверки условия используется внутренняя команда shell`а test.

while условие 
do 
команда 
   .  .  .
done

Пока условие истинно (то есть код завершения нулевой), выполняются команды из тела цикла. При замене служебного слова while на until условие продолжения цикла меняется на противоположное.

Приведем пример использования for-цикла. Усовершенствуем процедуру tree_args, чтобы она выдавала значения всех, а не только первых трех аргументов:

echo Идентификатор текущего процесса: $$
echo Имя команды: $0
echo Число фактических аргументов: $#
echo Совокупность всех аргументов: $@
i=1
for arg
do
  echo Значение аргумента ${i}: $arg
i=`expr $i + 1`
done

Генерация имен файлов

После всех подстановок, прежде чем команда начнет выполняться, в каждом ее составляющем слове ищутся символы *, ? и [. Если находится хотя бы один из них, то это слово рассматривается как шаблон имен файлов и заменяется именами файлов, удовлетворяющих данному шаблону. Файлы поставляются в алфавитном порядке. Если ни одно имя файла не удовлетворяет шаблону, слово остается неизменным. Трактовка символов *, ? и [: * Сопоставляется с произвольной цепочкой символов, в том числе и с пустой. ? Сопоставляется с произвольным символом. [...] Сопоставляется с любым из перечисленных в скобках символом. Пара символов., разделенных знаком -, обозначает отрезок алфавита, включающий сами указанные символы. Если сразу, вслед за [ идет !, шаблону удовлетворяет любой символ, не перечисленный в скобках.

Рассмотрим несколько примеров. Чтобы подсчитать суммарное число строк во всех фортрановских файлах текущего каталога, достаточно выполнить команду

wc -l *.f

В качестве второго примера приведем фрагмент одного из стартовых файлов ОС UNIX:

for f in/etc/rc2.d/S*
do
   /bin/sh ${f} start
done

В цикле будут в алфавитном порядке запускаться файлы из каталога /etc/rc2.d, имена которых начинаются на S.

Теперь рассмотрим команду

rm -f .*.[Bb]

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

Читателю предлагается самостоятельно ответить на вопрос, удалит ли команда

rm -f*.[Bb][AaUu]

файл с именем .bu.

Экранирование

Под экранированием будем понимать защиту от интерпретации shell`ом таких символов, как пробел (разделитель слов в командной строке), *, ?, [(элементы шаблона имен файлов) и некоторых других. Каждый из перечисленных символов может быть экранирован, то есть представлять самого себя, если перед ним стоит / или он расположен между кавычками ("" или "").

Как всегда, приведем несколько примеров. Пусть нужно умножить значение переменной i на 5. К цели ведет строка

i="expr $i * 5"

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

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

ls -al | grep "Oct 3"

сделает то, что нужно. Кавычки использованы для экранирования двух пробелов в разыскиваемой цепочке символов. Без них команда grep попыталась бы найти текст Oct в файле с именем 3.

Переназначение ввода/вывода

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

>файл Использовать заданный файл для стандартного вывода. Если файла нет, он создается; если есть, он опустошается. >>файл Использовать файл для стандартного вывода. Если файл существует, то выводимая информация добавляется в конец; в противном случае файл создается. <файл Использовать файл для стандартного ввода. <<слово Читать информацию из текущего файла стандартного ввода, пока не встретится строка, совпадающая с заданным словом, или конец файла.

Если любой из этих конструкций предшествует цифра, она определяет дескриптор (вместо подразумеваемых дескрипторов 0 или 1), который будет ассоциирован с файлом, указанным в конструкции. Например, строка

... 2>protocol

переназначает стандартный протокол (дескриптор 2) в файл по имени protocol.

Чтобы переназанчить стандартный протокол туда же, куда уже назначен стандартный вывод, следует употребить конструкцию

... 2>&1

Приведем два примера. Пусть нужно измерить время выполнения некоторой команды, направив ее результаты в файл cmd.res, а данные о времени - в файл cmd.time. К цели ведет строка

time команда >cmd.res 2>cmd.time

Второй пример довольно сложен, но очень красив. Он заимствован из книги "B.W. Kernighan, R. Pike. The UNIX Progpamming Environment. - Prentice Hall, 1984". Пусть нужно собрать воедино несколько файлов с возможностью их последующей распаковки (подобная проблема встала, например, при передаче по электронной почте содержимого целого каталога). Общая идея состоит в том, чтобы результатом упаковки стала shell-процедура, разворачивающая содержащую в ней информацию. Так сказать, мы хотим изготовить посылку, которая сама себя откроет и выставит присланное на стол. И поможет нам в этом чтение фрагмента стандартного ввода вплоть до указанного контекста (конструкция <слово). Отметим только, что строки комментария в языке shell начинаются символом #.

Следующая shell-процедура, которую мы назовем sharc, объединяет файлы-аргументы и выдает результат на стандартный вывод.

echo "# Вызвать данную shell-процедуру"
for i
do
    echo "echo $i>&2"
    echo "cat >$i <<"End of $i" "
    cat $i
    echo "End of $i"
done

В цикле for последовательно обрабатываются аргументы процедуры. Для каждого из них создается двухстрочный заголовок (echo ..., cat ...), после которого помещается содержимое файла и хвостовик (End of ...).

Пусть, например, имеются два файла, a.sh:

ls *.txt

и b.sh:

echo $@

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

sharc a.sh b. b.sh > arc.sh

в файле arc.sh окажется следующий текст:

# Вызвать данную shell-процедуру
echo a.sh >&2
cat >a.sh <<"End of a.sh"
ls *.txt
End of a.sh
echo b.sh >&2
cat >b.sh <<"End of b.sh"
echo $@
End of b.sh

Если теперь переслать куда-нибудь файл arc.sh и там выполнить команды

chmod +x arc/sh #Разрешить выполнение
arc.sh

произойдет выделение файлов и их наполнение нужным содержимым. Об этом позаботятся команды вида

cat >a.sh <<"End of a.sh"

которые прочитают после себя ровно столько строк, сколько нужно (данная команда будет читать вплоть до строки End of a.sh), и направят их в соответствующий файл (a.sh).

Обратите внимание на экранирование метасимволов от преждевременной интерпретации shell`ом (нам нужно было поместить метасимволы, такие как > и <<, в результирующий файл), а также на то, что внутри двойных кавычек выполняется подстановка значений переменных.

Читателю предлагается самостоятельно придумать способ борьбы с тем маловероятным обстоятельством, что в тексте упаковываемого файла встретится строка End of имя_файла.

Окружение процессов

Окружение - это набор пар (имя, значение), который передается выполняемой программе наряду со списком аргументов. Иными словами, порождаемые процессы наследуют окружение процесса-предка. Компонентами окружения являются, помимо прочих, следующие переменные и их значения: HOME Подразумеваемый аргумент команды cd - основной каталог пользователя. PATH Список имен каталогов для поиска команд. Элементы списка разделяются двоеточием. Пустой элемент означает текущий каталог. PS1 Основное приглашение (по умолчанию "$"). TERM Тип пользовательского терминала. TZ Информация о часовом поясе.

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

env

Поясним смысл переменных PATH и TZ.

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

:/bin:/usr/bin:/usr/lbin

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

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

MDT-3

описывают часовой пояс Москвы. Разница со всемирным временем составляет 3 часа.

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

Чтобы лучше уяснить ситуацию, рассмотрим пример. Пусть есть две shell-процедуры - proc1 и proc2.

# Процедура proc1
TZ=MEZ-1
echo Из proc1: $TZ
proc 2

Пусть стандартное значение переменной TZ есть MDT-3. В результате выполнения процедуры proc1 на экране появится:

Из proc1: MEZ-1
Из proc2: MDT-3

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

С помощью конструкции

export имя

переменная с указанным именем помещается в окружение, то есть становится глобальной. Например, обычно shell-процедура /etc/profile, которая выполняется как составная часть процесса входа пользователя в систему, содержит строки вида:

export PATH
PATH=...

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

Вернемся к процедуре proc1 и добавим в нее команду export:

# Процедура proc1
export TZ
TZ=MEZ-1
echo Из proc1: $TZ
proc2

Теперь в результате выполнения процедуры proc1 на экране появится:

Из proc1: MEZ-1
Из proc2: MEZ-1

Мы видим, что окружение изменилось.

Внутренние команды shell`а

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

  • . файл - текстовая подстановка заданного файла
  • break - выйти из цикла
  • continue - перейти к следующей итерации цикла
  • cd - сменит текущий каталог
  • echo - выдать аргументы на стандартный вывод
  • eval - выполнить заданную команду
  • exec - сменить программу процесса
  • exit - завершить выполнение shell`а
  • getopts - разобрать командную строку
  • pwd -выдать имя текущего каталога
  • read - прочитать строку и разделить ее на слова
  • return - выйти из функции
  • set - изменить внутренние переменные shell`а
  • shift - переименовать формальные аргументы
  • test - вычислить условное выражение
  • trap - установить реакцию на сигналы
  • type - выдать способ интерпретации имени shell`ом
  • ulimit - установить максимальный размер файлов
  • umask - установить маску

Сделаем некоторые пояснения. С помощью команды exec можно сменить программу текущего процесса. Например, если пользователь привык работать в С-shell`е (программа /bin/csh), то он может перейти туда по команде

exec csh

При этом обычный shell бесследно исчезнет.

Команда read позволяет читать информацию со стандартного ввода и разбивать его на слова. При достижении конца файла read возвращает ненулевой код завершения. Читателю предлагается самостоятельно убедиться в том, что shell-процедура, содержащая текст

who|while read a b c; do cat $* >/dev/$b &done

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

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

цифра=слово

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

Команда set записывается в виде

set опция... аргумент...

Из опций упомянем только одну: -х Выодить команды и их аргументы непосредственно перед выполнением. После выполнения команды

set -x

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

set +x

Если в команде set заданы аргументы, они станут новыми значениями $1, $2 и т.д. Рассмотрим фрагмент shell-процедуры /etc/rc2, которая выполняется в процессе загрузки системы:

set"who -r"
if [ $9 = "S" ]
...

Команда who -r выдаст строку следующего вида:

. run-level 2 Oct 17 09:56 2 0 S

Слова из этой строки станут аргументами команды set. В результате значением $1 станет точка, значением $2 - слово run-level, ..., значением $9 - S, так что проверяемое условие окажется истинным.

Резюме

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