Глава 12. Истина - в командах

Введение в POSIX'ивизм
(C) Алексей Федорчук, 2005

Назад Содержание Вперед
Вниз

Глава 12. Истина - в командах

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

Содержание

Введение в CLI

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

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

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

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

Собственно описанию шеллов будет посвящена отдельная глава. В главе же настоящей я попробую совместить роскошное - понимание принципов командного интерфейса, - с необходимым - изучением базовых команд. А заодно - и с полезным. то есть описанием особенностей того самого мифического POSIX-шелла, наиболее последовательно воплощенных в стандартных оболочках Free- и NetBSD (т.н. /bin/sh и /bin/ash, соответственно - на самом деле это практически одно и то же). И потому все примеры, иллюстрирующие принципиальные вопросы, будут базироваться на наиболее используемых командах (мы ведь их понемножку продолжаем запоминать, правда?), построенных в соответствие с правилами POSIX-шелла. Однако при описании интерактивных возможностей командной строки рамки POSIX-шелла окажутся тесными - и тут придется обращаться к более "продвинутым" представителям этого семейства, например, bash и zsh, а также к C-shell.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$ to <Tab>
toc2cue	toc2mp3	toe	top	touch

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

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

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

Опции

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

$ ls
back/    Desktop/  Mail/   OpenOffice.org1.1.2/  soft/        work/
bin/     docs/     mnt/    other/                tmp/
daemon/  lena/     music/  signature             wallpapers/

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

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

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

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

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

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

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

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

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

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

$ mkdir -vpm 777 dir/subdir

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

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

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

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

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

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

$ ls -a

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

$ ls -l

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

drwxr-xr-x   5 alv  users  136 2004-08-04 10:58 back/
drwxr-xr-x   2 alv  users  232 2004-07-29 13:00 bin/
drwxr-xr-x  19 root root   720 2004-08-04 14:45 daemon/
drwx------   3 alv  users  136 2004-07-18 14:00 Desktop/
drwxr-xr-x  13 alv  users  328 2004-07-26 10:40 docs/
drwxr-xr-x   2 alv  users  176 2004-07-21 03:43 lena/
drwxr-xr-x  17 alv  users 1736 2004-08-04 17:51 Mail/
drwxr-xr-x   2 alv  users   48 2004-07-20 22:24 mnt/
drwxr-xr-x  18 alv  users  552 2004-08-05 09:03 music/
drwxr-xr-x   5 alv  users  416 2004-08-04 14:17 OpenOffice.org1.1.2/
drwxr-xr-x   5 alv  users  144 2004-07-22 01:22 other/
-rw-r--r--   1 alv  users   71 2004-07-22 12:20 signature
drwxr-xr-x  11 alv  users  272 2004-08-04 16:42 soft/
drwxr-xr-x   3 alv  users  360 2004-08-06 08:29 tmp/
drwxr-xr-x   2 alv  users  344 2004-07-20 22:22 wallpapers/
drwxr-xr-x  14 alv  users  376 2005-10-11 06:43 work/

Форма

$ ls -F

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

back/    Desktop/  Mail/   OpenOffice.org1.1.2/  soft/        work/
bin/     docs/     mnt/    other/                tmp/
daemon/  lena/     music/  signature             wallpapers/

А форма

$ ls --color=auto

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

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

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

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

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

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

$ ls /ushkki3qy

Все недостающие символы будут добавлены автоматически. А такая оболочка, как 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 -[options] [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).

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

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

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

В BSD-реализации команда ls не понимает опции--color=auto, и потому здесь аналогом приведенного псевдонима будет:

$ alias ls='ls -FG'

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

Маленькое отступление: приведенная форма определения псевдонима действительна для всех shell-совместимых оболочек. В оболочках же семейства C-shell синтаксис команды будет иным:

$ alias ls ls -FG

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

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

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

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

$ cp file1 file2
cp: overwrite  file2'?

И пользователь может решить, нужно ли ему затирать существующий файл, ответив 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'

и так далее.

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

Переменные

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

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

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

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

$ echo $PATH

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

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

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

$ cd /home/alv

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

$ cd $HOME

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

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

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

/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-совместимых шеллах. В оболочках семейства csh для определения переменных обоих видов существуют специальные команды. Так, переменная оболочки задается командой set с указанием имени переменной и ее значения, соединенных оператором присваивания:

set EDITOR=joe

А чтобы та же самая переменная имела силу для всего окружения (то есть была бы переменной среди) потребуется другая команда:

setenv EDITOR=joe

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

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

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

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

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

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

Ибо одно из великих достижений командного интерфейса 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.

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

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

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

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

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

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

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

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

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

$ 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. Перед запуском найденная команда может быть отредактирована стандартными средствами - с использованием управляющих последовательностей.

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

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

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

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

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

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

$ cp dir1/* dir2

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

$ 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 path/{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-файлов.

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

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

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

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

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

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

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

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

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

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

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

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

$ echo date

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

date

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

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

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

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

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

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

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

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

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

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

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

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

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

А в заключение этого раздела - еще немного терпения. Потому что было бы несправедливо не уделить чуть-чуть места тому, что придает командному интерфейсу 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) - в Unix, в отличие от DOS/Windows, архивирование и компрессия обычно рассматриваются как разные процедуры).

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

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

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

#!/path/shell_name

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

#!/bin/sh

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

#!/bin/bash

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

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

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

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

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

$ bash scriptname

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

$ . ./scriptname

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

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

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

$ ./scriptname

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

Понятие о функциях

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

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

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

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

Самая главная команда

Наверное, самую лучшую
На этой земной стороне
Хожу я и песенку слушаю,
Она шевельнулась во мне...
Булат Окуджава

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

Как как можно логадаться по прочтении предшествующих параграфов этой главы, команд в Unix'ах - немерянное количество. В свежеустановленной Linux-системе минималистского типа (вроде CRUX или Archlinux) их может быть штук 500-700, в минимальной установке BSD - около 800. И это все без учета Иксов и всяческих приложений.

К слову сказать - а как определить количество команд? Есть несколько способов, зависящих от используемой ОС, дистрибутива, командной оболочки. Например, во всех BSD-системах все команды из базового комплекта собраны в каталогах /bin, /sbin, /usr/bin и /usr/sbin - так что достаточно просто подсчитать количество входящих в них файлов, Например, вот так:

$ ls /bin /sbin /usr/bin /usr/sbin | wc

Как я уже говорил, во FreeBSD 5-й такой подсчет даст результат - 800-850 команд, в зависимости от версии и полноты установки. В Linux'е размещение исполняемых файлов базовой системы зависит от дистрибутива, однако в первом приближении такой способ подойдет и здесь.

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

Во-первых, команд, которые нужны постоянно, ежедневно и по много раз на дню - не так уж и много. И практически все эти команды имеют прозрачную (правда, английскую) этимологию, или представляют собой простую аббревиатуру от слов, обозначающих соответствующее действие: ls - от list, cp - от copy, mv - от move, rm - от removie, и так далее. Так что тут в запоминании может помочь не столько какой-либо специализированный источник, сколько элементарный англо-русский словарь.

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

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

do you wish to see all XXX possibilities (YYY lines)?

И если согласимся с этим предложением (нажав клавишу y), то нашему взору предстанет все доступное изобилие команд.

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

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

$ echo $PATH

обычно можно увидеть список вроде

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

тогда как для root'а к нему добавятся пути вроде

/sbin /usr/sbin /usr/local/sbin

Во-вторых, как-то уже упоминалось, что одна и та же команда может иметь более чем одно имя - например, /bin/bash и /bin/sh. Эти синонимичные имена (их не следует смешивать с псевдонимами - те в списке по нажатию табулятора не встречаются) представляют собой жесткие или символические ссылки на одну и ту же программу. Создавая, таким образом, иллюзию того, будто команд в системе больше, чем на самом деле.

В третьих, получаемый по нажатию табулятора список включает только файлы из каталогов, стандартно перечисляемых в переменной PATH. И некоторые программы, которые во многих современных дистрибутивах Linux часто норовят установиться в каталоги вида /opt/pkg_name, вполне могут и не попасть в список, если значения переменной PATH не скорректированы должным образом.

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

$ ls /{bin,sbin} /usr/{bin,sbin} /usr/local/{bin,sbin}

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

Команда man предназначена для вызова экранной документации в одноименном формате (Manual Pages, что на Руси ласково переводится как "тетя Маня"). А такая man-документация почти обязательно сопровождает любую уважающую себя программу для POSIX-систем. И устанавливается в принудительном порядке при инсталляции соответствующей программы в любом случае - разворачивается ли она из бинарного тарбалла или собирается из исходников.

Для файлов man-документации предназначены специальные каталоги. Обычно это /usr/share/man (иногда /usr/man) для man-страниц базовой системы и штатных компонентов дистрибутива, usr/X11R6/man - для документации по оконной системе Икс, usr/local/man - для документации к программам, самостоятельно собираемых пользователем (или устанавливаемых из портов). Впрочем, местоположение man-страниц может быть различным. Однако в большинстве случаев его легко определить, просмотрев значения специальной переменной - MANPATH:

$ echo $MANPATH

что даст примерно такую картину:

/usr/man:/usr/share/man:/usr/local/man:/usr/X11R6/man

Man-страницы в любой POSIX-системе разделены на восемь нумерованных групп, каждая из которых размещена в собственном подкаталоге: /usr/share/man1, /usr/share/man2 и т.д. Назначение этих групп следующее:

  1. man1 - команды и прикладные программы пользователя;
  2. man2 - системные вызовы;
  3. man3 - библиотечные функции;
  4. man4 - драйверы устройств;
  5. man5 - форматы файлов;
  6. man6 - игры;
  7. man7 - различные документы, не попадающие в другие группы (в том числе относящиеся к национальной поддержке);
  8. man8 - команды администрирования системы.

В BSD к ним добавляется еще и 9-я группа, man9 - man-страницы для разработчиков ядра системы.

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

Внутри групповых подкаталогов можно увидеть многочисленные файлы вида filename.#.gz. Последний суффикс свидетельствует о том, что man-страница упакована компрессором gzip (что бывает не всегда, но - как правило). Цифра после имени соответствует номеру группы (то есть в подкаталоге ~/man1 это всегда будет единица). Ну а имя man-страницы совпадает с именем команды, которую она описывает. Если, конечно, речь идет о команде - в разделе 2 оно будет образовано от соответствующего системного вызова, в разделе 2 - от имени функции, и так далее. Но пока нас интересует только информация о командах, так что дальше я этого оговаривать не буду.

Для вызова интересующей документации требуется дать команду man с аргументами - номером группы и именем man-страницы, например:

$ man 1 ls

Причем номер группы необходим только в том случае, если одноименные документы имеются в разных группах. Для пользовательских команд он обычно не нужен, так как все равно просмотр групповых каталогов идет сначала в man1, затем - в man8, и только потом - во всех остальных (в порядке возрастания номеров).

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

$ man tty

Если же ему требуется получить сведения о драйвере устройства /dev/tty, номер группы должен быть указан в явном виде:

$ man 4 tty

Однако, повторяю, в данный момент нас интересуют только команды. Так что для получения информации, например, по команде ls достаточно ввести следующее:

$ man ls

после чего можно будет лицезреть примерно такую картину:

LS(1)	FSF	LS(1)
NAME
	ls - list directory contents
SYNOPSIS
	ls [OPTION]... [FILE]...
DESCRIPTION
	List  information  about
	the FILEs (the current directory by default).
	Sort entries alphabetically if none
	of -cftuSUX nor --sort.

То есть в начале man-страницы даются имя команды, которую она описывает (ls), ее групповая принадлежность (1 - пользовательские команды) и авторство (в данном случае - FSF, Free Software Foundations), или название системы. После чего обычно дается обобщенный формат вызова (SYNOPSIS) и краткое описание.

Следующая, основная, часть man-страницы - описание опций команды, и условия их применения. Далее иногда (но, к сожалению, не всегда) следуют примеры использования команды (Examples) в разных типичных ситуациях. В заключении, как правило, даются сведения о найденных ошибках (Bug Report) и приведен список man-страниц, тематически связанных с данной (See also), с указанием номера группы, к которой они принадлежат, иногда - историческая справка, а также указываются данные об авторе.

Большинство man-страниц занимают более одного экрана. В этом случае возникает необходимость перемещения по экранам и строкам - т.е. некоторая навигация. Впрочем, сама по себе команда man не отвечает не только за навигацию по странице. но даже за ее просмотр. Для этой цели она неявным образом вызывает специальную программу постраничного просмотре - т.н. pager (это - совсем не то, чем дети лохов в песочнице ковыряются). В Linux таковым по умолчанию выступает уже известная нам команда less, во FreeBSD - more (впрочем, здесь это - жесткие ссылки на одну и ту же программу). Хотя пользователь может определить для себя какой-либо другой pager - это такая же переменная, как и пользовательская оболочка или редактор, и устанавливается точно так же:

export PAGER=more

в POSIX-совместимых шеллах, или

setenv	PAGER	more

в C-shell и его производных.

Однако не будем оригинальничать, сохранив less в качестве средства просмотра man-страниц (лично я предпочитаю ее, вызываемую в формате more, для чего определяю alias less='less -M'). В этом случае с навигационными ее возможностями можно ознакомиться. нажав клавишу h - вызов встроенной помощи команды less. Из которой мы и узнаем, что перемещаться по man-странице можно с помощью управляющих последовательностей, сходным в принципе с теми, с которыми мы ознакомились в предшествующей главе.

Управляющие последовательности команды less для большинства навигационных действий весьма разнообразны, но в принципе разделяются на две группы: чисто буквенные и состоящие из комбинаций Control+литера. Так, переместиться на одну строку вперед можно просто нажатием клавиши j, на одну строку назад - клавиши k, сместиться на экранную страницу - с помощью клавиш f (вперед) и b (назад). Однако того же результата можно доиться комбинациями клавиш Control+n и Control+p для построчного перемещения и Control+v и Control+и - для постраничного (вперед и назад, соответственно).

Аналогично и для большинства других действий (смещение на половину экранной страницы, например: Control+D и d - вперед, Control+U и u - назад) можно обнаружить минимум одну альтернативную пару управляющих последовательностей. Регистр символов обычно значения не имеет. Одно из исключений - нажатие клавиши g перемещает к первой строке man-страницы, клавиши G - к последней.

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

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

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

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

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

Возвратимся, однако, к нашей man-документации. Для навигации по странице можно использовать и обычные клавиши управления курсором, клавиши PgUp/PgDown, а также некоторые другие. Например, нажатие Enter приводит к смещению на одну строку вперед (аналогично клавише Down, а клавиши Spacebar - на один экран вперед (подобно PgDown.

Однако это - не лучший способ навигации. Потому что управляющие последовательности (не зависимо, в стиле ли vi, или в стиле emacs) обладают дополнительной полезной возможностью: они понимают числовые аргументы. То есть если мы нажмем клавишу с цифрой 5, а вслед за ней - клавишу J, то мы сместимся на пять строк вперед, комбинация 3+K - на три страницы назад, и так далее.

Есть и возможность поиска внутри man-страницы. Для этого нажимается клавиша прямого слэша (/), после чего вводится искомое слово (последовательность символов). Для выхода из просмотра man-страницы предусмотрена клавиша q. Кроме того, можно использовать и почти универсальную комбинацию для прекращения выполнения программ - Control+C.

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

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

$ man -k print

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

Исчерпывающим руководством по использованию системы Manual Pages является ее собственная man-страница. Доступ к ней осуществляется по команде

$ man man

которая выводит на экран man-страницу, содержащую описание команды man (эко загнул, а?):

MAN(1)	FreeBSD General Commands Manual	MAN(1)

NAME
	man -- format and display the on-line \
	manual pages

SYNOPSIS
	man [-adfhkotw] [-m machine] \
	[-p string] [-M path] [-P pager] \
	[-S list] [section] name ...

DESCRIPTION
     Man formats and displays the on-line manual pages.
	...

Система man-страниц имеет три кардинальных недостатка. Первый, о котором я уже говорил, - то, что она даст ответ только в том случае, если пользователь знает, как и о чем ее спрашивать. К сожалению, он не устраним. Вернее, устранить его можно только чтением всякого рода вводных стетай и книг (например, этой). А также, конечно, тех же manual'ов - в попытках постичь заложенную в них сермяжную правду. Уверяю, что момент истины рано или поздно наступит...

Второй недостаток - в том, что подавляющее большинство man-страниц входит в состав дистрибутивов (Linux ли, Free- или прочих BSD) только в английском варианте (а также часто на прочих распространенных языках - датчан и разных там прочих шведов). Конечно, в Рунете (а также в составе отечественных Linux-дистрибутивов) можно найти много переведенных man-страниц, однако часто они существенно устарели по сравнению с оригиналом. И потому неизбежно приходится читать их на языке Вильяма нашего, Шекспира.

Что, впрочем, не так уж и страшно - даже для тех, кто не очень свободно читает Шекспира в подлиннике. В свое время я обнаружил, что переведенные man-страницы подчас менее понятны, чем оригинальные. И вообще, от общения с документацией на тех языках, из которых я знаком только с ненормативной лексикой (типа испанского), у меня сложилось впечатление, что абсолютно без разницы, на каком языке написан данный Manual - лишь бы алфавит был понятен. Хотя те, кому приходилось обращаться к японо-язычной документации (за отсутствием любой иной), утверждают, что и это не имеет большого значения. Иначе говоря - POSIX'ивист POSIX'ивиста всегда поймет, "будь он грек, черкес или менгрел" ((с) Тимур Шаов). На крайний случай - Виктор Вислобоков ведет большой проект по полному переводу всех man-страниц Linux на русский язык. А специфически Free'шные страницы в переводе можно найти здесь.

Третий же, и, пожалуй, главный недостаток - сложность навигации внутри объемных man-страниц и невозможность ориентации внутри группы связанных по смыслу документов, - призвана устранить система info. Она принята в качестве стандартной для программ проекта GNU. И многие из них в полном объеме документированы только в этом формате.

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

Документация в формате man и info - далеко не единственный источник информации о POSIX-системах и их приложениях. Многие свободные программы сопровождаются html-, ps- и pdf-файлами, есть и иные способы представления (типа docbook, например). Правда, некоторые спартанско-ориентированные дистрибутивы Linux (CRUX и Archlinux тому примером) при установке программ безжалостно вырезают из них всякую крамолу, за исключением man-страниц (включая и излишнюю документацию). А во FreeBSD соответствующим настройками (редактированием файла /etc/make.conf и сопуствующих) также можно запретить инсталляцию всякой дополнительной документации. Так что если в вашей системе не обнаружится дополнительных источников информации - стоит поискать их на сайте разработчиков интересующей вас программы...

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

[ опубликовано 30/08/2005 ]

- Глава 12. Истина - в командах   Версия для печати