Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

"Вечные истины" POSIX-систем
Истина четвертая, или In command veritas

Алексей Федорчук
alv@linux-online.ru

Source - http://linuxshop.ru/unix4all/?cid=&id=421

Нам уже пришлось говорить о командах, и даже парочку использовать на практике. Потому что командный интерфейс (интерфейс командной строки, Command Line Interface, он же CLI) - это третья из вечных POSIX-истин, постижение которой позволит пользователю взаимодействовать с системой.

Введение в CLI

Конечно, современный POSIX-мир не обделен и т.н. "графическими" пользовательскими интерфейсами. Правда, термин этот не удачен - основу таких интерфейсов составляет визуальное манипулирование объектами, а уж в каком режиме оно осуществляется, текстовом или графическом, - дело десятое. И потому этот класс программ правильнее было бы называть объектными интерфейсами, или там визуальными. Хотя, чем черт не шутит, а вдруг появится завтра система с голосовым интерфейсом? - только про voice mouse мозги мне не пудрите... Так что пока, за неимением гербовой, сохраняем общепринятое обозначение - графический пользовательский интерфейс (Graphics User Interface, сокращенно называемый GUI или, по нашински, по бразильскому, - Гуевым).

Опять, однако, отвлекся... Так вот, Гуевых интерфейсов в POSIX-системах - что грязи, хороших, разных и не очень (не очень разных или не очень хороших - ненужное зачеркнуть). Однако роль CLI от этого меньше не становится. Ибо CLI а) исконен в Unix'ах, б) универсален, и в) представляет собой базу, для которой Гуи всякого рода являют лишь оболочку.

Поясню последнюю мысль. Всякое действие в POSIX-системе может быть выполнено прямой командной директивой (что это - станет понятно чуть позже). И его же можно осуществить путем манипулирования объектами. Например, копирование файлов выполняется соответствующей командой - cp, это первый способ. Но его же можно осуществить перетаскиванием мыши объекта, представляющего наш файл зрительно, из того места, где он находился ранее, туда, где мы хотим видеть его копию, а это уже второй способ. И так почти во всем. То есть манипуляция объектами в GUI - это обычно более или менее опосредованное выполнение соответствующей команды. Почему основные навыки работы с CLI не помешают даже тому пользователю, который не вылезает из графической среды.

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

Как легко догадаться из определения, кроме предоставления пользовательского интерфейса, шеллы выполняют и еще одну функцию - служат интерпретаторами собственных языков программирования, Сам по себе второй момент для нас сейчас не существенен. Однако именно на нем основывается классификация шеллов. Ибо программ таких существует изрядное множество, которое можно разделить на две группы, которые обычно именуются - Bourne-shell совместимые и C-shell совместимые. В силу ряда причин в качестве стандарта POSIX принята одна из оболочек первой группы - так называемый POSIX-шелл, Правда, он представляет собой чистую абстракцию, однако большинство используемых в Linux оболочек декларируют ту или иную степень совместимости с ним.

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

Командная строка

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

Командная директива (или просто команда) - основная единица, посредством которой пользователь взаимодействует с шеллом. Она образуется по определенным правилам, именуемым синтаксисом. Синтаксис командной директивы определяется, в первую очередь, языком, принятым в данной командной оболочке. Кроме того, некоторые команды (не очень многочисленные, но весьма употребимые) имеют собственный, нестандартный синтаксис.

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

Итак, командная директива образуется:

  • именем команды, однозначно определяющим ее назначение,,
  • опциями, определяющими условия выполнения команды, и
  • аргументами - объектами, над которым осуществляются действия.

Очевидно, что имя команды является обязательным компонентом, тогда как опции и аргументы могут и отсутствовать (или подразумеваться в неявном виде по умолчанию).

Еще один непременный компонент командной директивы - это специальный невидимый символ конца строки: именно его ввод отправляет команду на исполнение. В обыденной жизни этот символ вводится нажатием и отпусканием клавиши Enter. Почему обычно и говорят: для исполнения команды нажмите клавишу Enter. Тот же эффект, как правило, достигается комбинацией клавиш Control+M. Конца командной строки, знаменующего исполнения команды, мы на экране не видим. Однако важно, что это - такой же символ, как и любой другой (хотя и имеющий специальное значение). Это знание нам понадобится, когда речь дойдет до специальных символов вообще (и даже немного раньше).

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

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

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

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

Иногда команда, вызванная через имя своего синонима, может отличаться по своей функциональности от самой же себя, вызванной под родным именем. В этом случае говорят о эмуляции одной команды другой. Типичный пример - командная оболочка bash в большинстве дистрибутивов Linux имеет своего дублера - sh; вызванная таким образом, она воспроизводят функциональность того самого пресловутого POSIX-шелла, о котором я недавно говорил как о явлении мифическом.

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

Поясню на примере. Создание пустого файла выполняется командой touch. Чтобы ввести ее имя в строку, достаточно набрать первые три ее буквы - tou, - и клавишу Tab, остальные два символа будут добавлены автоматически. Если же мы из естественной человеческой лени ограничимся только двумя первыми символами, то после нажатия табулятора нам будет предложен список возможных дополнений:

$ to <Tab>
toc2cue  toc2mp3  toe      top      touch

из которого мы и выберем подходящее. В данном случае достаточно набора еще одной буковки и повторного нажатия на Tab. А вот если прибегнуть к табулятору в пустой командной строке - перед нами предстанет список имен всех команд, доступных в данной системе. Правда, перед этим обычно задается вопрос, а хотим ли мы созерцать эти 500-700 имен.

Большинство употребимых команд POSIX-систем - коротки и мнемонически прозрачны. И может показаться. что не такое уж это облегчение - заменить ввод двух-трех символов нажатием табулятора (а то еще и неоднократным). Однако когда речь дойдет до аргументов команд - тут вся мощь автодополнения станет явной.

И еще маленькое отступление. Автодополнение - стандартная возможность bash и всех других командных оболочек, относимых к категории развитых. Но как раз в стандарте POSIX эта возможность не предусмотрена, и потому POSIX shell ее лишен.

Опции

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

Исполнение же многих других команд невозможно без указания опций и (или) аргументов. Для них в ответ на ввод одного ее имени часто следует не сообщение об ошибке (или не только оно), но и краткая справка по использованию команды. Например, в ответ на ввод команды для создания каталогов mkdir (от make directory) последует следующий вывод:

usage: mkdir [-pv] [-m mode] directory ...

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

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

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

Продемонстрирую это на примере опций все той же команды mkdir. Полный их список будет следующим:

  -m, --mode=MODE	установить код доступа (как в chmod)
  -p, --parents	не выдавать ошибок если существует, создавать
                    родительские каталоги если необходимо
  -v, --verbose	печатать сообщение о каждом созданном
  				каталоге
  --help            показать помощь и выйти
  --version		вывести информацию о версии и выйти

Очевидно, что для опции --version краткая форма совпала бы с таковой для опции --verbose, и потому первая существует только в полной форме. А вот для опции --help краткая форма в большинстве команд возможна, и она выглядит как -h. Более того, во многих командах вызов помощи может быть вызван посредством опции -?. К слову сказать - приведенный выше список опций команды mkdir получен именно таким способом.

Раз уж зашла речь об опциях --version и -h (--help, -?), давайте и их запомним на будущее: как и опция -v, --verbose, смысл их идентичен почти во всех командах.

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

$ mkdir -vpm 777 dir/subdir

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

$ mkdir --parents --mode=777 dir/subdir

Загадочные семерки после опции -m (--mode) - это и есть те самые атрибуты доступа, данные в символьной нотации. Смысл их прояснится, надеюсь, до конца этой главы.

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

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

Различные же опции команды ls определяют состав и формат выводимого списка файлов. Так, в форме

$ ls -a

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

$ ls -l

дается вывод списка имен файлов в "длинном" формате (отсюда название опции -l - от long), то есть с указанием атрибутов доступа, принадлежности, времени модификации, размера и некоторых других характеристик, которые будут подробно рассмотрены в следующем разделе; форма

$ ls -F

позволяет получить список файлов с символьным различением файлов различных типов. Например, имя каталога будет выглядеть как dirname/, имя исполнимого файла - как filename* (здесь звездочка - не шаблон имени, а символическое обозначение исполняемого файла), и так далее; а форма

$ ls --color=auto

представит те же типы файлов в списке в различной цветовой гамме (впрочем, при некоторых условиях; и, добавлю, auto - лишь одно из возможных значений опции --color).

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

Порядок опций, если их приводится более одной, для большинства команд не существенен. Хотя, например, для команды tar, создающей файловые архивы, опция -f, значением которой является имя создаваемого или распаковываемого архива, традиционно указывается последней. И, к слову сказать, именно эта команда - одна из немногих, опции которой не обязаны предваряться символами дефиса. Так, директивы

$ tar cf filename.tar dir

и

$ tar -cf filename.tar dir

абсолютно равноценны: и та, и другая создает единый архивный файл filename.tar из отдельных файлов каталога dir.

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

Аргументы

Однако тема стандартного ввода (а равно и вывода) - у нас еще на горизонте. А пока мы подобрались к понятию аргументов командной директивы. Аргументами определяется, как правило, объект (или объекты) действия команды. В большинстве случаев в качестве аргументов команд выступают имена файлов и (или) пути к ним. Выше говорилось, что при отсутствии аргументов команда ls выводит список имен файлов текущего каталога. Это значит, что текущий каталог выступает как заданный неявным образом (по умолчанию) аргумент команды ls. Если же требуется вывести список имен файлов каталога, отличного от текущего, путь к нему должен быть указан в качестве аргумента команды явно, например:

$ ls /usr/local/bin

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

Так, если пользователь находится в своем домашнем каталоге (абсолютный путь к нему обычно выглядит как /home/username), то просмотреть содержимое каталога /usr/local/bin он может двумя способами - тем, который был дан в предыдущем примере, и вот так:

$ ls ../../usr/local/bin

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

Пути в аргументах команд могут быть весьма длинными. Например, чтобы просмотреть доступные раскладки клавиатуры, нужно дать команду примерно следующего вида:

$ ls /usr/share/kbd/keymaps/i386/qwerty

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

$ ls /u<Tab>sh<Tab>k<Tab>k<Tab>i3<Tab>q<Tab>y

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

$ ls /u/s/k/k/i/q

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

Большинство команд допускает указание не одного, а нескольких (и даже очень многих) аргументов. Так, единой директивой вида

$ cp file1 file2 ... fileN dir

можно скопировать (команда cp - от copy) сколько угодно файлов из текущего каталога в каталог dir (на самом деле на это "сколько угодно" накладываются некоторые ограничения, но практически предел очень далек).

Маленькое отступление. Упоминание команды cp - удобный случай чуть вернуться назад и рассмотреть одну очень важную опцию, почти универсальную для команд POSIX-систем. Для начала попробуем скопировать один каталог в другой:

$ cp dir1 dir2

Как вы думаете, что получится в результате? Правильно, сообщение о невозможности выполнения этой операции вида

cp: omitting directory 'dir1'

поскольку команда cp в чистом виде для копирования каталогов не предназначена. Что делать? Очень просто - указать опцию -R (от Recursive; в большинстве систем проходит и опция -r с тем же смыслом, но первая форма работает абсолютно везде). В результате в каталог dir2 не только будут скопированы сам каталог dir1 и все входящие в него файлы, но и вложенные подкаталоги из dir1, если таковые имеются.

Маленькое уточнение: вполне возможно, что в дистрибутиве, который имеется в вашем распоряжении, проходит и копирование каталогов просто через cp, без всяких дополнительных опций. Это - потому, что команда cp часто определяется как псевдоним самой себя с опцией рекурсивного копирования, о чем скоро пойдет речь в этом разделе.

Вообще рекурсия (то есть определение некоего выражения через самого себя) - очень важное понятие в Unix, пронизывающее происходящие от нее системы насквозь. И послужившие даже базой для своеобразного хакерского юмора. Вспомним GNU, что, согласно авторскому определению, означает - GNU is Not Unix. Однако сейчас для нас важно только то, что рекурсия применима практически ко всем файловым операциям, позволяя распространить действие одной командной директивы не только на файлы данного каталога, но и на все вложенные подкаталоги и их содержимое.

Однако вернемся к аргументам. Действие некоторых команд неоднозначно в зависимости от аргументов, к которым она применяется. Например, команда mv служит как для переименования файлов, так и для их перемещения в другой каталог. Как же она узнает, что ей делать в данном конкретном случае? Да именно по аргументам. Если дать ее в форме

$ mv filename1 filename2

то следствием будет переименование filename1 в filename2. А вот если первым аргументом указан файл, а вторым - каталог, например

$ mv filename dir

то результатом будет перемещение filename из текущего каталога в каталог dir. К слову сказать, команды типа mv воспринимают разное количество аргументов в зависимости от того, какие они, эти аргумента. В первом примере аргументов может быть только два - имя исходного файла и имя файла целевого. Зато во втором примере в качестве аргументов можно задать сколько угодно файлов и каталогов (с учетом вышеприведенной оговорки относительно "сколько угодно") - все они будут перемещены в тот каталог, который окажется последним в списке. То есть директивой:

$ mv file1 ... fileN dir1 ... dirM dirN

в каталог dirN будут перемещены все файлы file1 ... fileN и все каталоги dir1 ... dirM. Характерно, что для этого команде mv, в отличие от команды cp не требуется каких-либо дополнительных опций - она рекурсивна по самой своей природе.

Кое-что об исключениях

Итак, типичная форма POSIX-команды в обобщенном виде выглядит следующим образом:

$ command -[otions] [arguments]

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

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

$ find dir -option1 value -option2 [value]

Здесь dir - каталог, в котором выполняется поиск, - может рассматриваться в качестве аргумента команды. Опция -option1 (обратим внимание, что здесь, не смотря на многосимвольность опций, они предваряются единичным символом дефиса) и ее значение value определяют критерий поиска, например, -name filename - поиск файла с указанным именем, а опция -option2 предписывает, что же делать с найденным файлом (файлами), например, -print - вывести его имя на экран. Причем опция действия также может иметь значение. Например, значением опции -exec будет имя команды, вызываемой для обработки найденного файла (файлов). Так, директива вида

$ find ~/ -name *.tar -exec tar xf {} ;

требует отыскать в домашнем каталоге (~/), выступающем в качестве аргумента, файлы, имя которых (первая опция - критерия поиска) соответствует шаблону *.tar (значение первой опции), и выполнить (вторая опция - действия) в их отношении команду tar с собственными опциями, обеспечивающими распаковку архивов (значение второй опции). Интересно, что в этом контексте в качестве значений второй опции команды find выступает не только внешняя команда, но и все относящиеся к ней опции.

В последнем примере имеется несколько символов, смысл которых может показаться непонятным. Надеюсь, он прояснится достаточно скоро.

Псевдонимы и переменные

Вернемся на минуту к команде ls. У читателя может возникнуть вполне резонный вопрос: а если я всегда хочу видеть ее вывод в цвете, да еще с символическим различением типов файлов, да еще в "длинном" формате; ну и без вывода скрытых файлов мне никак не прожить. И что же - мне каждый раз вводить кучу опций, чтобы получить столь ,элементарный эффект?

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

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

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

$ alias ls='ls -F --color=auto'

Где следует обратить внимание на два момента: а) на то, что имя псевдонима совпадает с именем команды (что отнюдь не препятствует создания псевдонима типа ll='ls -l' специально для вывода файловых списков в длинном формате), и б) на одинарные кавычки, в которые заключено значение псевдонима. Смысл из станет ясен несколькими параграфами позже, а пока просто запомним, что кавычки (и именно одинарные - обязательный атрибут команды установки псевдонима).

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

Вспомним команды типа cp и mv, которыми мы, в частности, можем скопировать или переместить какие-то файлы из каталога в каталог. А что произойдет, если чисто случайно в целевом каталоге уже имеются файлы, одноименные копируемым/перемещаемым? Произойдет штука, могущая иметь весьма неприятные последствия: файлы в целевом каталоге будут заменены новыми, теми, что копируются туда или перемещаются. То есть исходное содержание этих файлов будет утрачено - и безвозвратно, восстановить его невозможно будет никакими силами.

Разумеется, иногда так и нужно, например, при резервном копировании старые версии файлов и должны быть заменены их более свежими вариантами. Однако такое приемлемо далеко не всегда. И потому в большинстве команд, связанных с необратимыми изменениями файловой системы, предусматривается специальная опция - -i (или --interactive). Если задать эту опцию с командой cp или mv, то при совпадении имен исходного и целевого файлов будет запрошено подтверждение на выполнение соответствующего действия:

$ cp file1 file2                                             10:13 pts/0
cp: overwrite  ile2'?

И пользователь может решить, нужно ли ему затирать существующий файл, ответив yes (обычно достаточно y), или это нежелательно, и должно ответить no (а также просто n - или не отвечать ничего, это равноценно в данном случае отрицательному ответу).

Так вот, дабы не держать в голове необходимость опции -i (ведь, как я уже говорил, пропуск ее в неподходящий момент может привести к весьма печальным результатам, в подавляющем большинстве систем для команд cp и mv (а также для команды rm, служащей для удаления файлов - в POSIX-системах эта операция также практически необратима) определяются одноименные им псевдонимы такого вида:

$ alias cp='cp -i'
$ alias mv='mv -i'
$ alias rm='rm -i'

Все это, конечно, очень благородно, заметит внимательный читатель. Но что, если мне заведомо известно, что сотни, а то и тысячи файлов целевого каталога должны быть именно переписаны новыми своими версиями (как тут не вспомнить про случай с резервным копированием)? Что же, сидеть и, как дурак, жать на клавишу Y?

Не обязательно. Потому что все команды рассматриваемого класса имеют еще опцию -f (в "длинной" своей форме, --force, она также практически универсальна для большинства команд). Которая, отменяя действие опции -i, предписывает принудительно переписать все файлы целевого каталога их обновленными тезками. И никто не мешает нам на этот случай создать еще один псевдоним для команды cp, например:

$ alias cpf='cp -f'

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

Есть и другой способ обойти опции, установленные для команды-псевдонима: просто отменить псевдоним. Что делается командой обратного значения

$ unalias alias_name

То есть дав директиву

$ unalias cp

мы вернем команде копирования ее первозданный смысл. Ну а узнать, какие псевдонимы у нас определены в данный момент, и каковы их значения, еще проще: команда

$ alias

без опций и аргументов выведет полный их список:

la='ls -A'
less='less -M'
li='ls -ial'
ll='ls -l'
ls='ls -F --color=auto'

и так далее.

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

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

Что такое переменная? Ответ просто - некоторое имя, которому присвоено некоторое значение. Не очень понятно? - Согласен. Но, возможно, станет яснее в дальнейшем.

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

Таким встроенных переменных довольно много. И одна из первых по значению - переменная PATH. Это - список каталогов, в которых оболочка, в ответ на ввод пользователя в командной строке, ищет исполнимые файлы - то есть просто команды. Вы, наверное, обратили внимание, что во всех приведенных выше примерах имена команд указывались без всяких путей к ним (в отличие от файлов-аргументов, путь к которым - обязателен). Так вот, успех ее поисков и определяется списком значений переменной PATH. Каковые могут быть просмотрены командой echo

$ echo $PATH

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

/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin

Тем временем вернемся к переменной PATH Значение ее - полный абсолютный путь к домашнему каталогу пользователя. То есть, чтобы перейти в него, пользователю по имени alv вместо

$ cd /home/alv

достаточно набрать

$ cd $HOME

и он в своих владениях. Может показаться, что экономия - грошовая, но минуту терпения - и выгоду от использования переменных вы увидите.

Кроме переменных, предопределенных в шелле, пользователю предоставляется почти полная свобода в определении переменных собственных. И вот тут-то и наступает ему обещанное облегчение при наборе аргументов команд.

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

/home/alv/data/all.my.works/geology/plate-tectonics

Весьма удручающе для набора, даже если исправно работать табулятором для автодополнения, не так ли? Прекрасно, упрощаем себе жизнь определением переменной:

$ PLATE=/home/alv/data/all.my.works/geology/plate-tectonics

Дело в шляпе, Теперь, если нам нужно просмотреть состав этого каталога, достаточно будет команды

$ ls $PLATE

А вызвать из него любой файл для редактирования можно так:

$ joe $PLATE/filename

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

$ NAME=Value

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

Так вот, этот самый субшелл не наследует переменные родительской оболочки. И в итоге запущенная из командной строки программа ничего не будет знать, например, о путях к исполняемым файлам. Что автоматически ведет к невозможности запуска из нее команд просто по имени, без указания точного пути.

Чтобы избежать такой неприятной ситуации, было придумано понятие переменной окружения, или переменной среды - environment variable. Это - те переменные, которые наследуются от родительского шелла всеми дочерними программами. И чтобы сделать их таковыми, переменные следует экспортировать. Как? Командой export, которая может быть применена двояким образом. Можно сначала определить переменную:

$ NAME=Value

а затем применить к ней команду export:

$ export NAME

А можно сделать это в один прием:

$ export NAME=Value

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

$ NAME1=Value1
$ NAME2=Value2
...
$ NAMEN=ValueN

то проще прибегнуть к первому способу, так как команда export может иметь сколько угодно аргументов:

$ export NAME1 NAME2 ... NAMEN

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

Навигация и редактирование

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

В некоторых случаях имеет смысл различать "большое слово" и "малое". Первое включает в себя словоразделитель (например, пробел), в качестве же второго интерпретируются символы, лежащие между словоразделителями. Понятно выразился? Если не очень - надеюсь, станет яснее при рассмотрении примеров.

Подчеркнем, что командное "слово" прямо не соотносится ни с опциями, ни с аргументами команды. Введение этого понятия призвано просто облегчить навигацию в командной строке и ее редактирование.

Ибо одно из великих достижений командного интерфейса POSIX-систем, заценить которое могут в полной мере только те, кто застал времена "черного DOS'а", - это возможность перемещения внутри командной строки и внесения необходимых изменений в имя команды, ее опции и аргументы. Делается это различными способами.

Самый привычный и, казалось бы, очевидный - использование клавиш перемещения курсора Left, Right, End и Home, действующих в командной строке точно так же, как и в каком-нибудь ворд-процессоре для Windows (клавиши Up, Down, PageUp, PageDown зарезервированы для других целей). То есть они позволяют перемещаться на один символ влево и вправо. в начало и конец командной строки. А если добавить сюда еще клавиши Delete и Backspace, позволяющие удалять символы в позиции курсора или перед ней - то, казалось бы, чего еще желать?

Оказывается - есть чего, и самый очевидный способ навигации и редактирования оказывается не самым эффективным. Для начала заметим, что в общем случае привычные клавиши перемещения курсора и редактирования в POSIX-системах не обязаны работать также, как они делают это в DOS/Windows. Это зависит от многих причин, в том числе и исторических. Ведь POSIX-системы по определению предназначены работать на любых практически машинах (в том числе и на тех, клавиатуры которых таких клавиш просто не имели).

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

Основные управляющиеся клавиши, которые используются в таких последовательностях (и имеются на клавиатурах почти любых машин - как говорят в таких случаях, в любых типах терминалов) - это клавиши Control и Meta. Пардон - возразит внимательный читатель, - сколько я ни долблю по клавишам моей PC'шки, но клавиши Meta не замечал. Возражение принято, но: на PC-клавиатурах функции Meta выполняют либо а) нажатие и отпускание клавиши Escape, либо б) нажатие и удерживание клавиши Alt.

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

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

Так, действие клавишной комбинации Control+F (от Forward - в большинстве случаев регистр алфавитной клавиши у правляющей последовательности значения не имеет) эквивалентно нажатию клавиши Right - это перемещение на один символ вправо, комбинации Control+B (от Back) - нажатию Left (перемещение на один символ влево). Комбинации Control+A и Control+E действуют аналогично Home и End, перемещая курсор в начало и конец командной строки, соответственно, Ну а с помощью комбинаций Control+D и Control+H можно удалить единичный символ в позиции курсора или перед ней (также, как и клавишами Delete и Backspace, соответственно).

Предвижу резонный вопрос: а какие достоинства в комбинации клавиш Control+Что_то по сравнению с элементарными End или Left? Конечно, одно достоинство - очевидно: при массовом вводе команд (а также, забегая вперед, замечу - и любых иных наборов символов. от исходных текстов до романов), при использовании keybindings руки не отрываются от основной (алфавитно-цифровой) части клавиатуры. И в итоге, по приобретении некоторого минимального навыка, дело движется быстрее. Обосновать тестами не могу (тут какая-нибудь физиометрия понадобится), но не верящим - предлагаю попробовать.

Главное же преимущество клавиатурных последовательностей перед стандартными навигационными клавишами - много более широкая их функциональность. Я не случайно начал этот параграф с упоминания командных "слов" - это, наряду с единичными символами, также навигационные (и, добавлю, редакционные) единицы командной строки. То есть управляющие последовательности позволяют при навигации и редактировании оперировать не только единичными символами, но и целыми словами. Например, комбинация Meta+F смещает курсор на одно "слово" вперед, та же Meta в сочетании с B - на одно слово назад, и так далее. Прошу обратить внимание: действие алфавитной клавиши в комбинации с Meta сходно по смыслу ее сочетанию с клавишей Control, но как бы "усилено": последовательность Meta+D уничтожает не символ в позиции курсора, как это было бы для D в сочетании с Control, а все командное "слово".

Рассматривать ключевые последовательности подробно здесь я не буду: детали здесь зависят от командной оболочки и ее настроек, а этим темам будут посвящены специальные разделы в соответствующей главе). Отмечу только два существенных обстоятельства. Первое: keybindings предоставляют пользователю полный комплекс приемов для любых действий в командной строке - вплоть до преобразования регистров уже введенных символов и "слов" (из нижнего в верхний и наоборот), "перетасовки" символов в команде или ее аргументах, и так далее. И не только в командной строке - большинство популярных в POSIX-мире текстовых редакторов, от простых nano или joe до грандиозного Vim и монструозного emacs. построены по тому же принципу). Так что навыки, полученные при работе с keybindings, например, в bash, весьма поспособствуют виртуозному освоению любого из этих инструментов.

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

История команд

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

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

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

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

В качестве опции можно указать желаемое количество одновременно выведенных команд. Например, директива

$ history -2

выведет две последние команды из буфера истории вместе с их номерами:

1023  joe shell.html
1024  less ~/.zshrc

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

$ !1023

для приведенного выше примера повторно откроет файл shell.html в текстовом редакторе joe.

Другой способ доступа к командам из буфера истории - комбинации клавиш Control+P и Control+N, служащие для последовательного его просмотра (как бы "пролистывания") назад и, соответственно, вперед (разумеется, если есть куда). Они дублируются клавишами управления курсором Up и Down (назад и вперед, соответственно). Кроме того, последовательности Meta+ и Meta+> обеспечивают переход к первой и последней команде в буфере истории.

Любая извлеченная (с помощью стрелок или управляющими последовательностями) из буфера истории в текущую строку команда может быть повторно запущена на исполнение - нажатием клавиши Enter или дублирующей ее комбинацией Control+M. Причем предварительно ее можно отредактировать - изменить опции, или аргументы, - точно так же, как и только что введенную.

Во всех современных "развитых" шеллах предусмотрены средства поиска команды в буфере истории - простым перебором (обычно Meta+P - назад и Meta+N - вперед). Впрочем, не смотря на громкое название, это ничем практически не отличается от обычного пролистывания стрелками. Что при обширной истории команд может быть весьма утомительным. И потому для ее облегчения предусмотрена такая интересная возможность, как наращиваемый поиск (incremental search) нужной команды в буфере истории по одному (или нескольким) из составляющих ее символов.

Выполняется он так: после нажатия (при пустой командной строке) клавишной комбинации Control+R появляется предложение ввести алфавитный символ (или - последовательность символов произвольной длины), заведомо входящий в состав требуемой команды:

$
bck-i-search: _

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

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

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

Регулярные выражения

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

В соответствие со своей леностью разработчики POSIX-систем придумывают способы, как бы им минимизировать свои усилия. А пользователи из лени изощряются в применении этих приемов на практике. В частности - в том, как свести к минимуму набор в командной строке.

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

Элементарная, и весьма частая, в духе школьных, задача: из каталога dir1 требуется скопировать все файлы в каталог dir2. Так неужели все они должны быть перечислены в качестве аргументов команды cp. Нет, нет, и еще раз нет. Ибо для этой цели придуманы шаблоны имен файлов. Самый часто используемый из них - специальный символ * (вроде бы я о нем уже говорил?). Он подменяет собой любое количество любых символов (в том числе - и нулевое, то есть отсутствие символов вообще). То есть для решения предложенной задачи нам достаточно дать команду:

$ cp dir1/* dir2

Чуть усложним условия: к копированию из dir1 предназначены не все файлы, а только html-документы, традиционно имеющие расширение html (строго говоря, в POSIX-системах нет расширений в понимании DOS, но к этому мы когда-нибудь вернемся). Решение от этого не становится сложнее:

$ cp dir1/*html dir2

Обращаем внимание: в Linux, в отличие от DOS/Windows, шаблон * подменяет действительно любые последовательности символов, в том числе и точки в середине имени, то есть необходимости указывать шаблон как *.html, нет.

Однако тут можно вспомнить, что html-документы могут иметь и расширение htm (как известно, в DOS имя файла строится по схеме 8.3). Не пропустим ли мы их таким образом при копировании. Таким - безусловно, пропустим. Однако нам на помощь придет другой шаблон - символ ?. А соответствует он любому единичному символу (или - его отсутствию, т.е. символу Null). То есть, если команда из примера будет модифицирована таким образом:

$ cp dir1/*htm? dir2

то она гарантированно охватит все возможные маски html-документов.

Вроде все хорошо. Однако нет: из каталога dir1 нам нужно скопировать только три определенных файла - file1, file2, file3. Не придется ли каждый из них указывать в командной строке с полным путем (а ведь они могут быть и в глубоко вложенном подкаталоге типа dir1/dir11/dir111)? Все равно не придется, на сей предмет у нас есть прием с винтом - символы группировки аргументов, обозначаемые фигурными скобками. Что на практике выглядит так:

$ cp dir1/{file1,file2,file3} dir2

И приведет к единоразовому копированию всех трех файлов в каталог dir2. Заметим, что сгруппированные аргументы разделяются запятыми без пробелов. И еще: в оболочке bash группируемые аргументы придется полностью вводить руками. Но вот в zsh на них распространяется возможность автодополнения.

Группировка аргументов может быть сколь угодно глубоко вложенной. Так, команда

$ mkdir -p dir1/{dir11/{dir111,dir112},dir12/{dir121,dir122}}

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

И еще несколько примеров. Регулярное выражение для диапазона - то есть вида [...], подменяет любой из символов, заключенных в квадратные скобки. Символы эти могут даваться списком без пробелов (например, выражение [12345] соответствует любому символу от 1 до 5) или определяться в диапазоне, крайние значения которого разделяются дефисом без пробелов (эквивалентное первому выражение - [1-5]). Кроме того, символ ^, предваряющий список или диапазон, означает отрицание: выражение [^abc] подменяет любой символ, исключая символы a, b и c.

Последние примеры регулярных выражений могут показаться надуманными. Однако представим. что в том же каталоге dir1, кроме html-документов, содержатся также файлы изображений в различных форматах - GIF, JPEG, TIFF и так далее (традиционно имеющие одноименные расширения). И все они должны быть скопированы в каталог dir2, а вот как раз html-файлы нам в данный момент без надобности. No problemas, как говорят у них:

$ cp dir1/*[^html] dir2

И в каталоге dir2 окажется все содержимое каталога dir1, за исключением html-файлов.

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

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

Маленькое отступление. Команды, с многочисленными их опциями, особенно в полной форме, и аргументами могут оказаться весьма длинными, не укладывающимися в пределы экранной строки. Правда, обычно командная оболочка по умолчанию настраивается с разрешением так называемого word wrapping (то есть переноса "слов" команды без обрыва строки - последнее, как мы помним, достигается нажатием клавиши Enter или комбинации Control+M и приводит к немедленному исполнению введенной команды; если ввод ее не окончен - последует сообщение об ошибке). Однако перенос "слов" при этом происходит, как бог на душу положит. И в результате команда теряет читабельность и становится сложной для понимания.

Тут-то и приходит на помощь понятие экранирования, упомянутое абзацем выше. Знак экранирования - обратный слэш ( - превращает символ, имеющий специальное значение (а таковыми являются, например, упоминавшийся ранее шаблон в именах файлов - *), в самую обычную звездочку. А раз конец строки - тоже символ, хотя и специальный, то и он доступен для экранирования. Так что если завершить введенный фрагмент команды обратным слэшем (некоторые оболочки требуют предварить его пробелом, и лучше так и делать, хотя в bash пробел не обязателен), после чего нажать Enter, то вместо попытки исполнения будет образована новая строка. в которой можно продолжать ввод. Вид приглашения к вводу при этом изменится - это будет так называемое вторичное приглашение командной строки, которое я в дальнейшем буду обозначать как >, хотя и его представление настраиваемо.

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

У обратного слэша есть еще одна интересная особенность - я назвал бы ее инвертированием специального значения символов. Для примера: некая последовательность цифр (например, 033), введенная в командной строке, будет воспринята как набор обычных символов. Однако она же может выступать как код какого-либо символа (в частности, 033 - код символа Escape в восьмеричной системе счисления). И подчас возникает необходимость ввода таких кодов (тот же код для Escape. скажем, затруднительно ввести каким-либо иным образом). И вот тут обратный слэш проявляет свое инвертирующее действие: последовательность  будет восприниматься уже не как набор символов, а как код символа Escape (обратим внимание, что тут достаточно единичного слэша). Непосредственно в командной строке такой способ инвертированного экранирования обычно не используется, но находит широкое применение в сценариях. Почему и запомним этот прием - он со временем потребуется нам для русификации системы).

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

Buono Parte, но не все. В двойных кавычках сохраняют специальное значение метасимволы $ и , а также обратные кавычки (`), о назначении которых я скажу чуть позже. То есть в них сохраняется возможность, с одной стороны, получения значений переменных (как мы помним, с помощью $ИМЯ). А с другой стороны, если нам требуется дать символ бакса в его прямом и привычном значении, у нас есть возможность заэкранировать его обратным слэшем. И если потребуется вывести на экран сообщение "с вас, уважаемый, $500", то это можно сделать таким образом:

$ echo "с вас, уважаемый, $500"

Еще одно широко применяемое использование двойных кавычек - экранирование пробелов, предотвращающих разбиение аргументов команды на отдельные "слова". Правда, в случае с командой echo это, как правило, не требуется (хотя настоятельно рекомендуется экранировать ее аргумент таким образом). Однако представьте, что в качестве аргумента команды копирования и перемещения выступает файл, переписанный с Windows-машины. Ведь там пробелы в именах - вещь обычная. Тут-то экранирование двойными кавычками и придется к месту.

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

Завершая тему экранирования. Осталось сказать только об обратных кавычках. Их функция очень узка: они служат для экранирования команд. То есть, скажем, команда

$ echo date

в полном соответствие со своим именем, просто выведет нам собственный аргумент:

date

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

$ echo `date`
Втр Дек 16 11:45:12 MSK 2003

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

Конечно, в данном случае добиться той же цели можно было бы гораздо легче - просто командой date. Однако представьте, что у нас возникло желание одновременно и получить сведения о количестве пользователей в системе (для чего предназначена команда who). Тут-то и выясняется. что проще всего это сделать командой типа следующей:

$ echo "На момент `date` в системе зарегистрированы `who`"

Ответом на что будет сообщение, подобное тому, что часто можно наблюдать на главной странице многих сайтов:

На момент Втр Дек 16 12:11:36 MSK 2003 в системе зарегистрированы alv lis

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

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

Пора вернуться к генеральной линии моего рассказа - основам командного интерфейса. Надеюсь, из предшествующего читателю стало ясно, что подавляющее большинство команд в 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

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

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

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

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

Сценарии оболочки: первые представления

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

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

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

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

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

Сценарии в Unix-системах несколько напоминают batch-файлы, памятные многим по временам MS DOS, или макросы, знакомые пользователям любых развитых прикладных пакетов. Подобно тем и другим, они предназначены в первую очередь для автоматизации часто исполняемых рутинных операций. В частности, именно они позволяют избежать ввода длинных последовательностей в командной строке.

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

Создание пользовательского сценария - просто, как правда. Для этого всего и нужно:

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

С принципами создания команд и командных конструкций мы в первом приближении разобрались (или пытались разобраться) раньше. А вот способов помещения их в файл существует множество. Можно просто ввести (или вызвать из буфера истории) нужную команду и оформить ее как аргумент команды echo, вывод которой перенаправить в файл:

$ echo "cp -rf workdir backupdir" > mybackup

Таким образом мы получили простейший скрипт для копирования файлов из рабочего каталога в каталог для резервного хранения данных, что впредь и

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

$ cat > myarchive

и нажимаем Enter. После этого команда остается в ожидании ввода данных для помещения их в новообразованный файл. Не обманем ее ожиданий и проделаем это. Причем можно не жаться и выполнить ввод в несколько строк, например:

cd $HOME/archivedir
tar cf archive.tar ../workdir
gzip archive.tar

Завершив ввод тела скрипта, все той же клавишей Enter открываем новую строку и набираем комбинацию Control+D, выдающую символ окончания файла.

В результате получаем сценарий для архивирования в специально предназначенном для этого каталоге archivedir наших рабочих данных (командой tar), а заодно и их компрессии (командой gzip) - в Linux, в отличие от DOS/Windows, архивирование и компрессия обычно рассматриваются как разные процедуры).

Наконец, сценарий можно создать в любом текстовом редакторе. но это не так интересно (по крайней мере, пока). Да и стоит ли вызывать редактор ради двух-трех строк?

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

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

#!/path/shell_name

В данном случае восклицательный знак подчеркивает, что это - не комментарий, а указание (т.н. sha-bang) на точный абсолютный путь к исполняемому файлу оболочки, для которой наш сценарий предназначен, например,

#!/bin/sh

для POSIX-шелла, или

#!/bin/bash

для оболочки bash. Здесь следует подчеркнуть, что шелл, для которого предназначается сценарий, отнюдь не обязан совпадать с командной оболочкой пользователя. И полноты картины для замечу, что указание точного имени интерпретатора требуется не только для шелл-скриптов, но и для программ на любых языках сценариев (типа Perl или Python).

Так что по хорошему в обоих приведенных выше примерах ввод команд сценария следовало бы предварить строкой sha-bang. Конечно, отсутствие имени командной оболочки в явном виде обычно не помешает исполнению шелл-сценария - в Linux для этого будет вызван командный интерпретатор по умолчанию, то есть /bin/bash (файл /bin/sh, который обязан присутствовать в любой POSIX-системе, в Linux представляет собой символическую ссылку на /bin/bash и предназначен для имитации POSIX-шелла). Однако если сценарий предназначен для другой командной оболочки, то без sha-bang он может исполняться неправильно (а если оболочка еще и не из семейства совместимых с POSIX shell - то не исполняться вообще).

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

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

$ bash scriptname

Далее, для вызова скриптов существует специальная встроенная команда оболочки, обозначаемая символом точки. Используется она аналогично:

$ . ./scriptname

с тем только исключением, что тут требуется указание текущего каталога в явном виде (что и символизируется ./).

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

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

$ ./scriptname

Опять же - в предположении, что сценарий находится в текущем каталоге (./), иначе потребуется указание полного пути к нему. Что, понятно, лениво, но решается раз и навсегда: все сценарии помещаются в специально отведенный для этого каталог (например, $HOME/bin), который и добавляется в качестве еще одного значения переменной PATH данного пользователя.