Командные конструкции
Пора вернуться к генеральной линии моего рассказа - основам командного интерфейса. Надеюсь, из предшествующего изложения читателю стало ясно, что подавляющее большинство команд в 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
С понятием командных конструкций тесно связано понятие программ-фильтров. Это - команды, способные принимать на свой ввод данные с вывода других команд, производить над ними некоторые действия и перенаправлять свой вывод (то есть результат модификации полученных данных) в файлы или далее по конвейеру - другой команде. Программы-фильтры - очень эффективное средство обработки текстов, и в свое время мы к ним вернемся для подробного изучения.