Введение в POSIX'ивизм

       

Командные конструкции


Пора вернуться к генеральной линии моего рассказа - основам командного интерфейса. Надеюсь, из предшествующего изложения читателю стало ясно, что подавляющее большинство команд в POSIX-системах очень просты по сути и предназначены для выполнения какого-либо одного элементарного действия. То есть команда cp умеет только копировать файлы, команда rm - только удалять их, но зато делают они это хорошо. Подчас - черезчур хорошо, что мог ощутить на себе каждый, кому "посчастливилось" по ошибке выдать директиву вроде

$ rm -Rf *

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

Собственно, разделение любой задачи на серию элементарных операций - это и есть основной принцип работы в POSIX-системах, тот самый пресловутый Unix-way, о котором столько говорят его приверженцы (в которых с некоторых пор числит себя и автор этих строк). Однако вслед за этапом решительного размежевания (эх, неистребимы в памяти нашего поколения слова товарища Ленина) должен наступить этап объединения, как за анализом явления следует синтез эмпирических данных. И целям такого объединения служат командные конструкции.

Командные конструкции - очень важный компонент интерфейса командной строки. Они позволяют объединять несколько команд воедино и выполнять различные команды последовательно или параллельно. Для этого служат специальные символы - операторы: фонового режима, объединения, перенаправления и конвейеризации.

Простейшая командная конструкция - это выполнение команды в фоновом режиме, что вызывается вводом символа амперсанда после списка опций и (или аргументов):

$ command [options] [arguments] &

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

$ command1 & command2 & ... & commandN

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

Существуют и конструкции для последовательного выполнения команд. Так, если ряд команд разделен в строке символом точки с запятой (;)



$ command1 ; command2 ; ... ; commandN

то сначала будет выполнена команда command1, затем - command1 и так далее (молчаливо предполагается, что каждая из этих команд может иметь любое количество опций и аргументов; и, опять-таки, обрамление ; пробелами не обязательно во многих командных оболочках). Сами по себе команды не обязаны быть связанными между собой каким-либо образом - в сущности, это просто эквивалент последовательного их ввода в командной строке:

$ command1 $ command2 ...

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

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

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

$ ./configure $ make $ make install

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

$ ./configure ; make ; make install



может оказаться нецелесообразным.

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

$ ./configure && make && make install

На практике обе приведенные в качестве примера конструкции дадут один и тот же результат. Однако в ряде иных случаев различие между этими конструкциями может быть существенным.

Впрочем, предусмотрена и командная конструкция, в которой последующей команде предписано исполняться в том и только в том случае, если предыдущая команда завершилась неудачно. Она имеет вид

$ command1 command2

и может служить, в частности, для вывода сообщений об ошибках. Конечно, можно найти ему и другие применения (как, впрочем, и оператору &&), но это уже далеко выходит за рамки нашего элементарного введения.

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

И потому вкратце: любая команда получает данные для своей работы (например, список опций и аргументов) со стандартного устройства ввода (которым в первом приближении будем считать клавиатуру), а результаты своей работы представляет на стандартном устройстве вывода (коим договоримся считать экран монитора). А совсем-совсем недавно - из главы 8, - мы узнали, что в POSIX-системах любое устройство - не более чем имя специального файла, именуемого файлом устройства. И, таким образом, ничто не запрещает нам подменить специальный файл устройства ввода или устройства вывода любым иным файлом (например, обычным текстовым). Откуда и будут в этом случае браться входные данные или куда будет записываться вывод команды.

Перенаправление вывода команды обозначается следующим образом:



$ command > filename

или

$ command >> filename

В первом случае (одиночный символ >) вывод команды command образует содержимое нового файла с именем filename, не появляясь на экране. Или, если файл с этим именем существовал ранее, то его содержимое подменяется выходным потоком команды (точно также, как при копировании одного файла в другой, уже существующий). Почему такое перенаправление называется замещающим (или перенаправлением в режиме замещения).

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

Перенаправление ввода выглядит так:

$ command < filename

Конечно, теоретически можно представить себе и присоединяющее перенаправление ввода, однако практически оно вряд ли может найти применение.

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

$ ls dir1 > list

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

$ ls dir2 >> list

к этому списку добавится и содержимое каталога dir2.

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

$ sort < list

выведет на экран строки файла list, отсортированных в порядке возрастания значения ASCII-кода первого символа, а конструкция

$ sort -r < list

осуществит сортировку строк того же файла в порядке, обратном алфавитному (вернее, обратном порядку кодов символов, но это нас в данном случае не волнует).

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

$ sort -r < list > list_r

не только выполнит сортировку строк файла list (это - назначение команды sort) в обратном алфавитному порядке (что предписывается опцией -r, происходящей в данном случае от reverce), но и запишет ее результаты в новый файл list_r, а конструкция



$ sort -r < list >> list

добавит по-новому отсортированный список в конец существующего файла list.

Возможности построения командных конструкций не ограничиваются перенаправлением ввода/вывода: результаты работы одной команды могут быть переданы для обработки другой команде. Это достигается благодаря механизму программных каналов (pipe) или конвейеров - последний термин лучше отражает существо дела.

При конвейеризации команд стандартный вывод первой команды передается не в файл, а на стандартный ввод следующей команды. Простой пример такой операции - просмотр списка файлов:

$ ls -l | less

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

Конвейеризация команд может быть сколь угодно длинной. Возможно также объединение конвейеризации команд и перенаправления в одной конструкции. Кроме того, команды в конструкции могут быть сгруппированы с тем, чтобы они выполнялись как единое целое. Для этого группа команд разделяется символами ; и пробелами, как при последовательном выполнении команд, и заключается в фигурные скобки. Так, если нам требуется перенаправить вывод нескольких команд в один и тот же файл, вместо неуклюжей последовательности типа

$ command1 > file ; command2 >> file ; ... ; commandN >> file

можно прибегнут к более изящной конструкции:

$ { command1 ; command2 ; ... ; commandN } > file

Как и многие из ранее приведенных примеров, этот может показаться надуманным. Однако представьте, что вам нужно создать полный список файлов вашего домашнего каталога, разбитый по подкаталогам, да еще и с комментариями, в каком подкаталоге что находится. Конечно, можно вывести состав каждого подкаталога командой ls, слить их воедино командой cat (она предназначена, в частности, и для объединения - конкатенации, - файлов), загрузить получившееся хозяйство в текстовый редактор или ворд-процессор, где добавить необходимые словеса.


А можно - обойтись единой конструкцией:

$ { echo "List of my files" ; > echo "My text" ; \ ls text/* ; > echo "My images" ; \ ls images/* ; > echo "My audio" ; \ ls audio/* ; > echo "My video" ; \ ls video/* } > my-filelist

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

$ cat my-filelist List of my files My text text/text1.txt text/text2.txt My images images/img1.tif images/img2.tif My audio audio/sing1.mp3 audio/sing2.mp3 My video video/film1.avi video/film2.avi

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


Содержание раздела