Выбрать главу

Есть еще один способ установить значение переменной в порожденном интерпретаторе — присвоить его явно в командной строке перед самой командой:

$ echo 'echo $x' >echox

$ cx echox

$ echo $x

Hello Как и прежде

x не определено в порожденном интерпретаторе

$ x=Hi echox

Hi    Значение x передается порожденному интерпретатору

$

(Первоначально присваивания всюду в командной строке передавались команде, но это противоречило dd(1).)

Операцию '.' следует использовать, чтобы навсегда изменить значение переменной, тогда как присваивания в командной строке предназначены для временных изменений. В качестве примера рассмотрим еще раз поиск команд в каталоге /usr/games, не указанном в вашей переменной PATH:

$ ls /usr/games | grep fort

fortune Игровая команда fortune

$ fortune

fortune: not found

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin /usr/games не входит в PATH

$ PATH=/usr/games fortune

Позвони в звонок; закрой книгу; задуй свечу.

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin PATH не изменилось.

$ cat /usr/you/bin/games    команда games все еще здесь

$ . games

$ fortune

Непродуманная оптимизация - источник всех бед - Кнут

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin:/usr/games Сейчас PATH изменилось

$

Можно использовать оба средства в одном командном файле. Вы можете несколько видоизменить команду games для запуска одной игровой программы без изменения переменной PATH или постоянно переопределять PATH, чтобы она включала /usr/games:

$ cat /usr/you/bin/games

PATH=$PATH:/usr/games $*    Обратите внимание на $*

$ cx /usr/you/bin/games

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin /usr/games не входит

$ games fortune

Готов отдать свою правую руку, чтобы одинаково владеть обеими

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin Все еще не входит

$ . games

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin:/usr/games Теперь входит

$ fortune

Тот, кто медлит, иногда спасается

$

При первом обращении к games командный файл выполняется в порожденном интерпретаторе, в котором переменная PATH временно изменена так, чтобы включать каталог /usr/games. В то же время во втором примере файл обрабатывается текущим интерпретатором при значении $*, являющемся пустой строкой, поэтому в строке нет команд и переменная PATH изменяется. Применение команды games в обоих случаях достаточно нетривиально, но в результате получаем удобное и естественное для использования средство.

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

$ x=Hello

$ export x

$ sh           Новый интерпретатор

$ echo $x

Hello          x доступно в порожденном интерпретаторе

$ x='Good Bye' Изменим значение x

$ echo $x

Good Bye

$ ctl-d        Выйдем из порожденного интерпретатора

$              Снова в исходном интерпретаторе

$ echo $x

Hello          x по-прежнему Hello

$

Семантика команды export нетривиальна, но по крайней мере для повседневных нужд достаточно придерживаться основного правила: никогда не экспортируйте временные переменные, служащие для краткосрочных целей, и всегда экспортируйте переменные, необходимые вам во всех порожденных интерпретаторах (включая, например, интерпретаторы, запускаемые командой ! редактора ed). Поэтому переменные, имеющие специальное значение для интерпретатора, такие, как PATH и НОМЕ, следует экспортировать.

Упражнение 3.13

Почему в значение переменной PATH всегда включается текущий каталог? Куда его нужно поместить?

3.7 Еще раз о переключении ввода-вывода

Понятие стандартного потока диагностики было введено для того, чтобы сообщения об ошибках всегда появлялись на терминале:

$ diff file1 file2 >diff.out

diff: file2: No such file or directory

$

Без сомнения, сообщения об ошибке должны появляться подобным образом — было бы крайне неприятно, если бы они исчезли в файле diff.out, оставляя вас в уверенности, что ошибочная команда diff выполнена правильно.

В начале выполнения каждой программы определены по умолчанию три файла, обозначаемые небольшими целыми числами и называемые дескрипторами файла (мы рассмотрим их в гл. 7). Со стандартными входным (0) и выходным (1) потоками вы уже знакомы: они часто переключаются на файл или программный канал. Последний поток с номером 2 представляет собой стандартный поток диагностики и обычно предназначается для вывода на терминал.

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

$ time wc ch3.1

931 4288 22691 ch3.1

real 1.0

user 0.4

sys  0.4

$ time wc ch3.1 >wc.out

real 2.0

user 0.4

sys  0.3

$ time wc ch3.1 >wc.out 2>time.out

$ cat time.out

real 1.0

user 0.4

sys  0.3

$

Конструкция 2> имя_файла (между 2 и > не должно быть пробелов) переключает стандартный поток диагностики на файл; синтаксически она непривлекательна, но служит своей цели. (Для такого короткого теста, как приведенный выше, время, выдаваемое командой time, не совсем правильное, но для последовательности больших тестов она выводит полезную информацию, которой можно доверять в разумных границах. Вы вполне можете сохранить ее для дальнейшего анализа; обратитесь, например, к таблице 8.1.)

Допустимо также слияние двух выходных потоков:

$ time wc ch3.1 >wc.out 2>&1

$ cat wc.out

931 4288 22691 ch3.1

real 1.0

user 0.4

sys  0.3

$

Обозначение 2>&1 является указанием интерпретатору, что стандартный поток диагностики нужно поместить в тот же поток, что и стандартный выходной. Амперсанд не содержит какого-либо мнемонического смысла; это просто идиома, которую следует запомнить. Для добавления стандартного выходного потока к стандартному потоку диагностики можно использовать 1>&2: