Комбинирование команд

Перейдем теперь к главной гордости интерпретаторов Unix — комбинированию команд. Вначале рассмотрим его простейшие варианты.

Как уже говорилось, из одной командной строки в bash можно запустить несколько программ последовательно, записав соответствующие команды через точку с запятой (?;?), или параллельно, разделив их амперсандами (?&?). Имеется, конечно, и так называемая «труба» (pipe), перенаправляющая стандартный выход одной программы на стандартный вход другой. Из Unix она перекочевала и в DOS, но там, впрочем, реализована «халтурно»: даже при использовании интерпретатора DOS 7.x из командной оболочки Windows 9X, где ядро ОС имеет все необходимые средства, первая команда полностью отрабатывает перед началом выполнения второй, а данные передаются через временный файл.

Для управления порядком запуска программ, очевидно, необходимы операторные скобки. И в bash они действительно есть, причем даже двух видов — фигурные и круглые. Фигурные просто разделяют команды и показывают приоритеты, а круглые полностью изолируют выполняемые операции от «внешнего мира»: так, если внутри них меняются значения переменных окружения, на последующие команды это не влияет (см. рис. 1).

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

Команды можно скомбинировать и таким довольно неожиданным способом, как сделав одну параметром (либо иной частью) другой. Команда, заключенная в скобки, перед которыми ставится символ ?$?, или в обратные апострофы, выполняется, а результат работы (тот, что был бы выдан в стандартный выводной поток) подставляется на ее место в командную строку.

Рис. 2. Можно вставить одну команду в другую

Взгляните на рис. 2. Здесь мы сначала заносим в переменную окружения LC_ALL, определяющую параметры страны, значение english, а затем выводим дату на языке страны, указанной в файле /etc/sysconfig/i18n (там задаются стандартные настройки системы). Чтобы это сделать, мы записываем в LC_ALL новое значение. Какое же?

Давайте разберемся по порядку. Внутри кавычек (они, как обычно, поставлены на тот случай, если в значении, присваиваемом переменной LC_ALL, окажется пробел; в действительности это невозможно, но, как говорится, кашу маслом не испортишь) мы видим в обратных апострофах команду с именем ?.? и одним параметром /etc/sysconfig/i18n. Что она делает? Выполняет строки файла, указанного в качестве параметра, так, как если бы они были введены в этом месте в «натуральном виде». В частности, если в файле определяются переменные (а файл /etc/sysconfig/i18n содержит только определение переменных и комментарии), то они становятся доступны. Одну из них — LOCALE — мы выводим следующей командой, и она подставляется в командную строку в качестве значения, присваемого переменной LC_ALL.

Вместо обратных апострофов можно было использовать и конструкцию $(...). А очень похожая на нее конструкция $((...)) позволяет подставить в командную строку значение арифметического выражения (см. рис. 3; команда date с параметром +%s выдает текущую дату и время как число секунд, прошедших с 1 января 1970 г. до настоящего момента).

Рис. 3. Конструкция $((...)) — встроенный калькулятор bash

Разумеется, в bash есть и составные операторы. Но прежде чем к ним переходить, нужно рассказать о способах проверки условий, а для этого вспомнить, что в Linux, как и в DOS, каждая отработавшая команда сообщает системе код возврата.

Коды возврата и проверка условий

По общепринятому соглашению код возврата равен 0, если работа программы завершилась успешно, и другому числу в противном случае (рис. 4). Код возврата последней выполнившейся в данном сеансе команды содержится в переменной $?.

Рис. 4. Программа bzip2 в соответствии с общепринятым соглашением возвращает 0 при успешном завершении операции и 1 при неудачном

С этими кодами возможны разнообразные действия, из которых важнейшими являются логические операции: отрицание (?!?), конъюнкция (?&&?) и дизъюнкция (?||?), как это показано на рис. 5. (Не путайте конъюнкцию с параллельным запуском, а дизъюнкцию — с «трубой»!)

Заметьте, если результата выполнения первой команды достаточно для определения кода возврата всей конструкции, то вторая команда не вызывается: это видно из сравнения двух примеров использования ?||? на рис. 5.

Рис. 5. С кодами возврата возможны логические операции

Любое арифметическое выражение также можно записать как команду и получить для него код возврата (рис. 6). Обратите внимание на два последних примера. Не кажется ли вам, что тут замешана какая-то мистика: 0 дает в результате 1, а 2 — 0? Дело в том, что внутри арифметических операторов действуют соглашения языка Си, т. е. истиной является любое число, кроме нуля, а ложью — нуль, вовне же поступает обычный код возврата, для которого все наоборот.

Рис. 6. Арифметические выражения могут вырабатывать код возврата

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

Рис. 7. Команда [

Самыми первыми появились команды test и [, различающиеся только тем, что [ для симметрии требует последним параметром символа ]. Эти команды предназначены в основном для проверки разнообразных условий, связанных со свойствами файлов. Сравнивать с их помощью строки тоже можно, хотя и не очень удобно (рис. 7). Лучше пользоваться более мощной командой [[: она допускает сравнение с шаблоном и предусматривает более мнемоническую запись конъюнкции и дизъюнкции (рис. 8).

Рис. 8. Команда [[

Составные операторы

Теперь, наконец, можно перейти и к составным операторам. Их довольно много: условный оператор (см. листинг 1), оператор выбора (см. листинг 2), несколько операторов цикла: while (см. листинг 3), until, отличающийся от while только отрицанием условия, два цикла for (см. листинг 4 и листинг 5) и, разумеется, функции: они используются в основном в сложных скриптах, поэтому пример в листинге 6 — совсем игрушечный. Обратите, однако, внимание на конструкцию $??, позволяющую задавать спецсимволы тем же способом, что в языке Си: — табуляция, — конец строки и т. д.

Параметры и массивы

В bash, как и в командном интерпертаторе DOS, поддерживаются позиционные переменные, соответствующие параметрам скрипта, — $1, $2,.. В DOS они называются %1, %2 и т. д., и таким способом обозначаются параметры только до девятого, а если их больше, до следующих приходится добираться с помощью команды shift. Эта команда в bash тоже есть (как вы, наверное, догадались, она пришла в DOS из Unix), но кроме того, к любому параметру можно обратиться и непосредственно. Хотя в силу исторических причин интерпретатор воспримет запись $10 как переменную $1, за которой следует 0, форма ${10}, ${11} и т. д. будет понята правильно. А переменная $# содержит общее число параметров. Кстати, если нужно обратиться к последнему параметру, это удобно сделать в форме ${!#} (см. врезку «Извлечение значений переменных» в первой части статьи).

Для передачи всех параметров в другую программу в bash предусмотрены две специальные переменные: $* и $@. Переменная $* содержит список параметров; разделителем служит первый из символов, хранящихся в переменной окружения IFS (это список разделителей слов, используемый в основном командой read). А вот $@ магическим образом раскрывается в $# параметров (если используются кавычки, то результат бывает забавен — см. рис. 9). Так как перебор параметров скрипта — весьма частая операция, то in ?$@? в цикле for можно опустить, как это сделано в листинге 6.

Рис. 9. Переменные $* и $@

Ввод—вывод

Работа с файлами в bash основана на потоковом вводе-выводе. Каждый поток представлен в программе уникальным целым числом — дескриптором, который передается командам чтения и записи. (Точно так же обстоит дело и в DOS.) Для чтения чаще всего применяется команда read, а для записи — команда echo; с обеими мы уже много раз встречались в примерах.

Стандартному вводному потоку соответствуют файл /dev/stdin и дескриптор 0, стандартному выводному — файл /dev/stdout и дескриптор 1. Сообщения об ошибках направляются в файл /dev/stderr, открытый с дескриптором 2, а дескрипторы, начиная с третьего, используются по усмотрению программиста.

Операции перенаправления, из которых пользователям DOS знакомы >, >> и <, в bash работают не только со стандартными вводным и выводным потоками, но и с любыми иными. Например, чтобы открыть файл для чтения с дескриптором n, следует написать n<имя_файла, а чтобы открыть его для записи или для дописывания в конец (append) — соответственно n>имя_файла или n>>имя_файла. Дескриптор стандартного вводного потока в первом случае, а также стандартного выводного во втором и в третьем разрешается опустить, и тогда конструкции начинают выглядеть и работать так же, как в DOS. Запись &>имя_файла означает перенаправление в заданный файл дескрипторов 1 и 2 (т. е. стандартного ввода и сообщений об ошибках).

Запись &n соответствует файлу, открытому с дескриптором n (еще один способ обозначить такой файл — /dev/fd/n), поэтому написав, скажем, m>&n, мы направим поток m в тот же файл, что и поток n. Написав n<&-, мы закроем файл с дескриптором n.

В bash есть и другие операции перенаправления. Конструкция n<>имя_файла позволит открыть файл и для чтения, и для записи. А конструкция <<разделитель, используемая только в скриптах, перенаправляет в стандартный вводной поток следующие за ней строки файла скрипта вплоть до той, которая состоит из заданного разделителя. Ее вариант <<-разделитель дополнительно удаляет из всех перенаправленных строк начальные символы табуляции.

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

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

Об авторе

Виктор Хименко, khim@rnktech.com