Текст книги "UNIX — универсальная среда программирования"
Автор книги: Брайан Керниган
Соавторы: Роб Пайк
сообщить о нарушении
Текущая страница: 7 (всего у книги 31 страниц) [доступный отрывок для чтения: 12 страниц]
Глава 3
Возможности интерпретатора shell
Интерпретатор shell
– это наиболее важная программа для пользователей UNIX, быть может, за исключением вашего любимого текстового редактора. Она исполняет ваши запросы на запуск программ и занимает гораздо больше вашего времени, чем любая другая программа системы. Значительная часть настоящей главы и гл. 5 будут посвящены описанию возможностей интерпретатора. Основная мысль, к которой мы хотим подвести вас, состоит в том, что если вы научитесь работать с интерпретатором, то сможете достичь многого и без особого труда, не прибегая к традиционным языкам программирования типа Си.
Как уже отмечалось, описание интерпретатора разделено на две части. В этой главе от простейших возможностей, показанных в гл. 1, мы перейдем к рассмотрению некоторых необычных, но широко используемых конструкций, таких, как метасимволы, кавычки, новые команды с переданными им аргументами, переменные shell
и отдельные структуры управления. Все это понадобится вам для эффективной работы с интерпретатором. Материал гл. 5 более сложный. Изучив его, вы сможете писать настоящие программы на языке shell
и даже предоставлять их другим пользователям. Такое деление темы, конечно, во многом произвольно, поэтому мы рекомендуем вам прочитать обе главы.
3.1 Структура командной строки
Прежде чем продолжить рассмотрение, нужно уточнить, что такое команда и как она интерпретируется shell
. Этот раздел содержит более формальное описание и некоторую информацию об основных возможностях интерпретатора, описанных в первой главе.
Самая простая команда состоит из одного слова, обычно имени файла, предназначенного для выполнения (позднее вы познакомитесь с другими типами команд):
$ who
Выполняем файл /bin/who
you tty2 Sep 28 07:51
jpl tty4 Sep 28 08:32
$
Команда, как правило, завершается символом перевода строки, но может завершаться и точкой с запятой:
$ date;
Wed Sep 28 09:07:15 EDT 1983
$ date; who
Wed Sep 28 09:07:23 EDT 1983
you tty2 Sep 28 07:51
jpl tty4 Sep 28 08:32
$
Однако выполнение команды не начнется, пока вы не нажмете клавишу RETURN. Обратите внимание на то, что интерпретатор выдает только одно приглашение после нескольких команд, но если не учитывать этого, то ввод
$ date; who
идентичен вводу двух команд в разных строках. В частности, команда who
не будет выполняться до завершения date
. Попробуйте послать результат выполнения этих команд по программному каналу:
$ date; who | wc
Wed Sep 28 09: 08:48 EDT 1983
2 10 60
$
Возможно, вы получите не то, что ожидали, поскольку только результат команды who
передается команде wc
. При связывании who
и wc
через программный канал образуется единая команда, называемая конвейером, которая выполняется после date
. В процессе разбора командной строки shell
считает приоритет операции '|'
выше, чем операции ';'
. Для группирования команд следует использовать скобки:
$ (date; who)
Wed Sep 28 09:11:09 EDT 1983
you tty2 Sep 28 07:51
jpl tty4 Sep 28 08:32
$ (date; who) | wc
3 16 89
$
Результат выполнения команд date
и who
конкатенируется в один поток, который можно передать по программному каналу.
Информацию, поступающую по программному каналу, можно с помощью команды tee
собрать и поместить в файл (но не в другой программный канал). Команда tee
является частью интерпретатора shell
, но тем не менее удобна и при манипулировании программными каналами. Ее можно использовать для сохранения промежуточного результата в файле:
$ (date; who) | tee save | wc
3 16 89
Результат команды wc
$ cat save
Wed Sep 28 09:13:22 EDT 1983
you tty2 Sep 28 07:51
jpl tty4 Sep 28 08:32
$ wc
3 16 48
$
Команда tee
переписывает свой входной поток в поименованный файл (или файлы), а из него – точно так же без изменений в выходной поток, поэтому wc получает те же самые данные, как если бы команда tee
не присутствовала в конвейере.
В качестве еще одного символа, завершающего команду, применяют амперсанд (&
). Действие его аналогично действию символа перевода строки и точки с запятой, но он еще и указывает интерпретатору, что не нужно ждать завершения команды. Обычно &
используется для запуска фоновых, долго выполняющихся команд, в то время как вы продолжаете вводить новые команды в диалоге:
$ long-running-command &
5273
Номер процесса длительной команды
$
Приглашение появляется сразу
Имея возможность группировать команды, получаем некоторые интересные способы применения фоновых процессов. Команда sleep
ожидает указанное число секунд, прежде чем закончить свое выполнение:
$ sleep 5
$
Проходит 5 секунд до появления приглашения
$ (sleep 5; date) & date
5278
Wed Sep 28 09:18:20 EDT 1983
Результат второй команды date
$ Wed Sep 28 09:18:25 EDT 1983
Появляется приглашение, затем
через 5 секунд дата
Фоновый процесс начинается, но сразу "засыпает"; тем временем вторая команда date
выдает текущее время, а интерпретатор – приглашение для ввода новой команды. Пятью секундами позже прекращается выполнение команды sleep
, и первая команда date
выдает новое время. Трудно представить на бумаге истечение времени, поэтому вам следует попытаться самостоятельно реализовать этот пример. (Разница между двумя значениями времени может и не равняться в точности 5 с, в зависимости от загруженности машины и по ряду других причин.) Это удобный способ отложить запуск команды на будущее; рассмотрите также в качестве удобного механизма такой пример:
$ (sleep 300; echo Чай готов) &
Чай будет готов через 5 минут
5291
$
(Если в строке, следующей за командой echo
, есть символ ctl-g, то при появлении ее на экране зазвонит звонок.) В этих примерах нужны скобки, так как приоритет '&'
выше, чем у ';'
.
Символ &
может завершать команды, а поскольку конвейеры являются командами, в скобках для запуска конвейеров как фоновых процессов нет необходимости, поэтому
$ pr файл | lpr &
позволяет выдать файл на печатающее устройство, не ожидая окончания выполнения команды. Использование скобок дает тот же эффект, но требует введения большего числа символов:
$ (pr файл | lpr ) &
To же, что и в предыдущем примере
Большинство команд допускает наличие аргументов в командной строке, таких, как файл в предыдущем примере (аргумент команды pr
). Аргументами служат слова, разделенные пробелами и символами табуляции, которые обычно именуют файлы, предназначенные для обработки командой. Однако они рассматриваются просто как строки, и программа может интерпретировать их любым подходящим для нее способом. Например, команда pr допускает имена файлов, которые нужно напечатать, команда echo
посылает эхо своих аргументов без всякой интерпретации, а первый аргумент команды grep специфицирует строку-шаблон для поиска. И конечно, многие команды имеют необязательные параметры (флаги), задаваемые аргументами, начинающимися со знака “-
”.
Различные специальные символы, интерпретируемые shell
, такие, как <
, >
, |
, ;
и &
, не являются аргументами команд, запускаемых интерпретатором. Они управляют самим процессом запуска. Например,
$ echo Hello > junk
требует, чтобы интерпретатор запустил команду echo
с одним аргументом Hello
и поместил выходной поток в файл junk
. Строка > junk
не является аргументом команды echo
; она интерпретируется shell
, и echo
никогда ее "не увидит". На самом деле, данная строка может и не быть последней в командной строке:
$ > junk echo Hello
Это идентичный запуск, хотя и менее очевидный.
Упражнение 3.1
В чем состоит различие между следующими командами?
$ cat file | pr
$ pr
$ pr file
(С течением времени операция переключения
<
потеряла свою связь с программными каналами; "cat file |
" считается более естественным, чем "< file
".)
3.2 Метасимволы
Интерпретатор распознает еще ряд символов как специальные. Наиболее часто используется звездочка *
, указывающая, что нужно искать в каталоге имена файлов, у которых вместо *
может быть любая последовательность символов. Например,
$ echo *
есть не что иное, как некое подобие команды ls
. В гл. 1 мы не отметили, что во избежание проблем с именами '.'
и '..'
, которые присутствуют в любом каталоге, символы подстановки в именах файлов нельзя применять к именам файлов, начинающимся с точки. Правило таково: символы подстановки в именах файлов действуют на имена файлов, начинающихся с точки, только в том случае, если точка явно задана в шаблоне. Как обычно, "рассудительная" команда echo
прояснит ситуацию:
$ ls
.profile
junk
temp
$ echo *
junk temp
$ echo .*
. .. .profile
$
Символы со специальным значением, подобные *
, называются метасимволами. Существует множество метасимволов (в табл. 3.1 приводится их полный список, но некоторые символы мы обсудим только в гл. 5).
> | prog > file – переключить стандартный выходной поток в файл |
>> | prog >> file – добавить стандартный выходной поток к файлу |
< | prog < file – извлечь стандартней выходной поток из файла |
| | p1 | p2 – передать стандартный выходной поток p1 как стандартный выходной поток для p2 |
< | "Документ здесь": стандартный выходной поток задается в последующих строках до строки, состоящей из одного символа str |
* | Задает любую строку, состоящую из нуля или более символов, в имени файла |
? | Задает любой символ в имени файла |
[ccc] | Задает любой символ из [ccc] в имени файла (допустимы диапазоны, такие, как 0-9 или a-z ) |
; | Завершает команды: p1; p2 – выполнить p1 , затем p2 |
& | Выполняет аналогичные функции, но не ждет окончания p1 |
`...` | Инициирует выполнение команд(ы) в ... ; `...` заменяется своим стандартным выводом |
(...) | Инициирует выполнение команд(ы) в ... в порожденном shell |
{...} | Инициирует выполнение команд(ы) в ... в текущем вызове shell (используется редко) |
$1, $2, ... | Заменяются аргументами командного файла |
$var | Значение переменной var в программе на языке shell |
${var} | Значение var ; исключает коллизии в случае конкатенации переменной с последующим текстом (см. также табл. 5.3) |
| c – использовать непосредственно символ c , перевод строки отбрасывается |
'...' | Означает непосредственное использование |
"..." | Означает непосредственное использование, но после того, как $ , `...` и будут интерпретированы |
# | В начале слова означает, что вся остальная строка рассматривается как комментарий (но не в седьмой версии) |
var=value | Присваивает value переменной var |
p1 && p2 | Предписывает выполнить p1 ; в случае успеха выполнить p2 |
p1 || p2 | Предписывает выполнить p1 ; в случае неудачи выполнить p2 |
Таблица 3.1: Метасимволы shell
При таком количестве метасимволов интерпретатора необходимо иметь возможность экранировать специальный символ от интерпретации. Самый простой и надежный способ экранирования – заключить его в апострофы:
$ echo '* * *'
* * *
$
Можно также использовать кавычки "..."
, но интерпретатор на самом деле "заглядывает" внутрь этих кавычек в поиске метасимволов $
, '...'
и , так что не применяйте
"..."
, если только вам не требуется определенным образом обработать строку в кавычках.
Еще одну возможность дает ввод обратной дробной черты перед каждым символом, который вы хотите закрыть от интерпретатора, например:
$ echo ***
Хотя строка ***
не похожа на английское слово, в терминологии языка shell
это слово, ибо им является любая последовательность символов, воспринимаемая интерпретатором как целое, включая даже пробелы, если они взяты в кавычки.
Кавычки одного вида могут экранировать кавычки другого вида:
$ echo "Don't do that!"
Don't do that!
$
и могут не заключать в себе весь аргумент:
$ echo x'*'y
x*y
$ echo '*'A'?'
*А?
$
В последнем примере команда echo
получает один аргумент, не содержащий апострофов, так как, сделав свое дело, апострофы исчезают. Строки в кавычках могут содержать символы строк:
$ echo 'hello
> world'
hello
world
$
Символ >
является вторичным приглашением интерпретатора, которое выдается, если ожидается продолжение ввода для завершения команды. В этом примере апостроф в первой строке должен быть уравновешен другим апострофом. Вторичное приглашение интерпретатора хранится в переменной PS2
; его можно изменить по своему вкусу.
Во всех приведенных выше примерах экранирование специальных символов предохраняет их от интерпретации. Команда
$ echo x*y
выдает все имена файлов, начинающиеся с x
и кончающиеся y
. Как обычно, команда echo
ничего "не знает" ни о файлах, ни о метасимволах; интерпретация *
, если она требуется, осуществляется shell
.
Что произойдет, если ни один файл не будет соответствовать шаблону? Интерпретатор просто пропустит строку, как если бы она была взята в кавычки, а не выразит вам свое неудовольствие (как было принято в ранних версиях). Конечно, не следует рассчитывать на это свойство, но его можно использовать, чтобы узнать о существовании файлов, соответствующих шаблону:
$ ls x*y
x*y not found
Сообщение ls: таких файлов нет
$ >xyzzy
Создать файл xyzzy
$ ls x*y
xyzzy
Файл xyzzy соответствует x*y
$ ls 'х*y'
x*y not found
ls не интерпретирует *
$
Появление обратной дробной черты в конце строки требует продолжения строки, что является способом задать интерпретатору очень длинную строку:
$ echo abc
> def
> ghi
abcdefghi
$
Обратите внимание на то, что символ перевода строки отбрасывается, если перед ним стоит обратная дробная черта, но он остается, если взят в кавычки. Метасимвол #
в программе на языке shell
практически всюду используется в качестве комментария; если слово начинается с #
, остаток строки игнорируется:
$ echo hello#there
hello
$ echo hello # there
hello # there
$
Символ #
не присутствует в оригинальной седьмой версии, но имеет очень широкое распространение, и в дальнейшем мы будем им пользоваться.
Упражнение 3.2
Объясните результат выполнения команды
$ ls .
Некоторые дополнительные сведения о команде echo
Команда echo
выдает заключительный символ перевода строки, даже если на это нет явного запроса. Разумной и, возможно, более корректной была бы такая реализация команды echo
, при которой вывод соответствовал бы только запросу. Добиться этого легко, если потребовать от интерпретатора выдачи приглашения:
$ правильное эхо введенная команда:
Введенная команда: $
Нет завершающего перевода строки
Однако при таком решении в самой распространенной ситуации, когда перевод строки нужен, он не подразумевается по умолчанию и требует дополнительного ввода:
$ правильное эхо 'Привет!
>'
Привет!
$
Поскольку команда должна по умолчанию выполнять наиболее часто встречающееся действие, настоящее эхо автоматически добавляет перевод строки.
Но как быть, если это нежелательно? В седьмой версии системы команда echo
имеет единственный флаг -n
, который подавляет последний символ перевода строки:
$ echo -n Enter a command:
Enter a command: $
Приглашение на той же строке
$ echo -
–
Только – является специальным случаем
$
Существует одна маленькая хитрость в случае получения эха от -n
, за которым должен следовать символ перевода строки:
$ echo -n '-n
>'
-n
$
Такое решение некрасиво, но эффективно, к тому же это довольно редкий случай.
Другой подход принят в System V, где команда echo
интерпретирует последовательность символов с обратной дробной чертой аналогично тому, как это делается в языке Си, а именно: b
обозначает "шаг назад", c
подавляет перевод строки (правда, здесь не очень точно воспроизведена конструкция Си):
$ echo 'Введенная команда: с'
Версия System V
Введенная команда: $
Хотя при подобном решении не возникает коллизий при получении эха от знака "-"
, у него есть свои недостатки. Команда echo
часто используется в качестве диагностического средства, а символ обратной дробной черты интерпретируется таким множеством программ, что участие в этом команды echo
только вносит дополнительную путаницу.
Итак, обе реализации команды echo
имеют и положительные, и отрицательные стороны. Мы будем использовать вариант седьмой версии (-n
), поэтому, если ваша команда echo
выполняется по-другому, несколько приводимых ниже примеров потребуют незначительных изменений.
Возникает еще один, философский, вопрос: что должна делать команда, если ей не передали аргументов, в частности, следует ли ей выдавать пустую строку или вообще ничего не предпринимать? Как вы уже знаете, все настоящие реализации команды выдают пустую строку, но в ранних версиях все было иначе. По этому поводу велись большие дебаты, а Д. МакИлрой привнес в них даже элемент мистицизма.
UNIX и Эхо
Жила-была в стране Нью-Джерси UNIX, прекрасная девушка, к которой приезжали издалека, чтобы полюбоваться ею. Ослепленные чистотой UNIX, все искали ее руки и сердца: одни – за изящество, другие – за изысканную вежливость, третьи – за проворность при выполнении самых изнурительных заданий. Была она от рождения столь великодушна и услужлива, что все женихи остались довольны ею, а ее многочисленное потомство распространилось во все концы земли.
Сама природа покровительствовала UNIX и вторила ей более охотно, чем кому-либо из смертных. Простые люди поражались ее эхом, таким оно было точным и кристально чистым. Они не могли поверить, что ей отвечают те же леса и скалы, которые так искажают их собственные голоса. Когда один нетерпеливый пастушок попросил UNIX: "Пусть эхо ответит ничего", и она послушно открыла рот, эхо промолчало. "Зачем ты открываешь рот?" – спросил пастушок. – "Отныне никогда не открывай его, если эхо должно ответить ничего!" – и UNIX подчинилась.
"Но я хочу совершенного исполнения, даже если эхо отвечает ничего," – потребовал другой, обидчивый, юноша, – "а никакого совершенного эха не получится при закрытом рте". Не желая обидеть никого из них, UNIX согласилась говорить разные "ничего" для нетерпеливого и обидчивого юношей. Она называла "ничего" для обидчивого как '
n'
. Однако теперь, когда она говорила 'n'
, на самом деле она не произносила ничего, поэтому ей приходилось открывать рот дважды: один раз, чтобы сказать'n'
, и второй раз, чтобы не сказать ничего. Это не понравилось обидчивому юноше, который тотчас сказал: "Для меня'n'
звучит, как настоящее "ничего", но когда ты открываешь рот второй раз, то все портишь. Возьми второе "ничего" назад". Услужливая UNIX согласилась отказаться от некоторых эхо и обозначила это как'c'
. С тех пор обидчивый юноша мог услышать совершенное эхо "ничего", если он задавал'n'
и'c'
вместе, но говорят, что он так и не услышал его, поскольку умер от излишеств в обозначениях.
Упражнение 3.3
Предскажите, что сделает команда
grep
в каждом случае, а затем проверьте себя;
grep $ grep \
grep \$ grep \\
grep \\$ grep "$"
grep '$' grep '"$'
grep ''$ grep "$"
Файл, состоящий из таких команд, послужит хорошим материалом для теста, если вы хотите поэкспериментировать.
Упражнение 3.4
Как указать
grep
, что нужно найти шаблон, начинающийся с'-'
? Почему взятие аргумента в кавычки не помогает? Подсказка: исследуйте флаг-е
.
Упражнение 3.5
Рассмотрите команду
$ echo */*
Может ли она вывести все имена всех каталогов? В каком порядке появятся эти имена?
Упражнение 3.6
(Хитрый вопрос.) Как ввести
/
в локальное имя файла (т.е. символ/
, который не является разделителем компонентов в абсолютном имени)?
Упражнение 3.7
Что произойдет в случае ввода команд
$ cat x y >y
и$ cat x >>x
Подумайте, прежде чем броситься их выполнять.
Упражнение 3.8
Если вы введете
$ rm *
почему команда
rm
не сможет предупредить вас, что вы собираетесь удалить все ваши файлы?
3.3 Создание новых команд
Теперь, как мы обещали вам в гл. 1, рассмотрим создание новых команд из старых. Имея последовательность команд, которую придется многократно повторять, преобразуем ее для удобства в "новую" команду со своим именем и будем использовать ее как обычную команду. Чтобы быть точными, предположим, что нам предстоит часто подсчитывать число пользователей с помощью конвейера
$ who | wc -l
(см. гл. 1), и для этой цели нужна новая программа nu
.
Первым шагом должно быть создание обычного файла, содержащего 'who | wc -l'
. Можно воспользоваться вашим любимым редактором или проявить изобретательность:
$ echo 'who | wc -l' >nu
(Что появится в файле nu
, если не употреблять кавычки?)
Как отмечалось в гл. 1, интерпретатор является точно такой же программой, как редактор, who
или wc
; он называется sh
. А коль скоро это программа, ее можно вызвать и переключить ее входной поток. Так что запускаем интерпретатор с входным потоком, поступающим из файла nu
, а не с терминала:
$ who
you tty2 Sep 28 07:51 rhh tty4 Sep 28 10:02
moh tty5 Sep 28 09:38 ava tty6 Sep 28 10:17
$ cat nu who | wc -l
$ sh < nu
4
$
Результат получился таким же, каким бы он был при задании команды who | wc -l
с терминала. Опять-таки, как и большинство программ, интерпретатор берет входной поток из файла, если он указан в качестве аргумента; вы с тем же успехом могли задать:
$ sh nu
Однако досадно вводить "sh
" каждый раз; во всяком случае эта запись длиннее и создает различия между программами, написанными, например, на Си, и программами, написанными с помощью shell
.[9] 9
Тем не менее такое различие существует в большинстве операционных систем
[Закрыть] Поэтому если файл предназначен для выполнения и если он содержит текст, то интерпретатор считает, что он состоит из команд. Такие файлы называются командными. Все, что вам нужно сделать, это объявить файл nu
выполняемым, задав
$ chmod + x nu
а затем вы можете вызывать его посредством
$ nu
С этого момента те, кто используют файл nu, не смогут определить способ его создания.
Способ, с помощью которого интерпретатор на самом деле выполняет nu
, сводится к созданию нового процесса интерпретатора, как если бы вы задали
$ sh nu
Этот процесс-потомок называется порожденным интерпретатором, т.е. процессом интерпретатора, возбужденным вашим текущим интерпретатором. Но команда sh nu
– это не то же самое, что sh < nu
, поскольку в первом случае стандартный входной поток все еще связан с терминалом. Пока команда nu
выполняется только в том случае, если она находится в вашем текущем каталоге (при условии, конечно, что текущий каталог включен в PATH
, а именно это мы и предполагаем с настоящего момента). Чтобы сделать команду nu частью вашего репертуара независимо от того каталога, с которым вы работаете, занесите ее в свой собственный каталог bin
и добавьте /usr/you/bin
к списку каталогов поиска:
$ pwd /usr/you
$ mkdir bin
Создать bin, если его еще не было
$ echo $PATH
Проверить Path, чтобы убедиться
:/usr/you/bin:/bin:/usr/bin
Должно быть нечто похожее
$ mv nu bin
Установить команду nu в bin
$ ls nu
nu not found
Она действительно исчезла
из текущего каталога
$ nu
4
Но интерпретатор ее находит
$
Конечно, ваша переменная PATH
должна быть правильно определена в файле .profile
, чтобы вам не приходилось переопределять ее при каждом входе в систему.
Существуют и другие простые команды, которые вы можете адаптировать к среде по своему вкусу и создавать таким же способом. Нам показалось удобным иметь следующие команды:
• cs
для посылки подходящей последовательности специфических символов с целью очистки экрана вашего терминала (24 символа перевода строки – практически универсальное решение);
• what
для запуска who
и ps -а
, чтобы сообщить, кто работает в системе и что он делает;
• where
для вывода идентифицированного названия используемой системы UNIX. Это удобно, если вы постоянно работаете с несколькими версиями. (Установка PS1
служит для подобной цели.)
Упражнение 3.9
Просмотрите каталоги
/bin
и/usr/bin
, чтобы выяснить, как много команд являются в действительности командными файлами. Можно ли это сделать с помощью одной команды? Подсказка: посмотритеfile(1)
. Насколько точно предположение, основанное на длине файла?