Аргументы
Однако тема стандартного ввода (а равно и вывода) - у нас еще на горизонте. А пока мы подобрались к понятию аргументов командной директивы. Аргументами определяется, как правило, объект (или объекты) действия команды. В большинстве случаев в качестве аргументов команд выступают имена файлов и (или) пути к ним. Выше говорилось, что при отсутствии аргументов команда 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 не требуется каких-либо дополнительных опций - она рекурсивна по самой своей природе.
||' Filename Вывод команды grep
Весьма часто при обработке текстов встает такая задача: заменить одно слово (или последовательность слов) на другое сразу во многих файлах. Как она решается "подоконными" средствами? Обычно - открытием всех подлежащих изменению документов в word-процессоре и применением функции поиска/замены последовательно в каждом из них.
Таким же способом можно воспользоваться и в POSIX-мире. Это просто, но уж больно скучно. Тем паче, что здесь есть очень эффективная альтернатива - средства потокового (неинтерактивного ) редактирования, примером которых является sed.
Потоковое, или неинтерактивное, редакторование не требует загруки документа в память (то есть открытия), как в обычных текстовых редакторах и word-процессорах. Нет, при нем подлежащий изменению файл (или группа файлов) обрабатываются построчно с помощью соответствующих команд, задаваемых как опции единой командной директивы. В наши дни это выглядит анахронизмом, однако в ряде случаев оказывается чрезвычайно эффективным. Каких? - ответ легко дать на нескольких конкретных примерах.
Так, при установке некоторых дистрибутивов Linux из числа Source Based вполне возможна ситуация, когда в какой-то момент времени в распоряжении пользователя не окажется никакого обычного текстового редактора. А необходимость внесения мелких изменений в конфигурационные файлы (например, в файл /etc/fstab) - возникнет. Так что делать - бежать срочно устанавливать свой любимый vim или emacs? И то, и другое - дело отнюдь не пяти минут. И тут самое время вспомнить про sed, который обязательно будет присутствовать в системе (поскольку он используется во многих установочных сценариях базовых пакетов).
Другой случай - во многих десятках, а то и сотнях, файлов требуется изменить одну-единственную строку, причем - одинаковым образом (например, заменить копирайт Васи Пупкина на Петю Лавочкина). Неужто для этой цели нужно вызывать мощный текстовый редактор, грузить в него немерянное количество документов, перемещаться тем или иным способом перемещаться к нужному фрагменту, вносить требуемое изменение? Отнюдь, ибо sed поможет и здесь, позволив выполнить изменение любого количества файлов в один в пакетном режиме.
Во всем блексе sed показывает себя при редактировании очень больших файлов (одно пролистывание которых требует немалого времени). А также - при редактировании сложных символьных последовательностей в нескольких файлах. Однажды, после очередной реконструкции моего сайта, передо мной встала задача тотальной модификации всех внутренних ссылок. Долго я с ужасом размышлял, как буду делать это в текстовом редакторе, и сколько ошибок при этом насажаю. Пока, раскинув мозгами, не нашел, как сделать это с помощью sed - быстро и, главное, безошибочно.
В самом общем виде sed требует двух аргументов - указания встроенной его команды и имени файла, к которому она должны быть применена. Впрочем, в качестве аргумента можно задать только простую команду, мало-мальски сложное действие (а команды поиска/замены принадлежат к числу сложных) необходимо определить через значения опции -e, заключенные в кавычки (одинарные или двойные - по ситуации). Что же касается имен файлов - то их в качестве аргументов можно указать сколько угодно, в том числе и с помощью масок типа *, *.txt и так далее. Правда, sed не обрабатывает содержимое вложенных подкаталогов, но это - дело поправимое (как - скоро увидим). Так что поиск и замена слова или их последовательности выполняются такой конструкцией:
$ sed -e 's/Вася Пупкин/Петя Лавочкин/' *
Здесь s - это команда поиска, Вася Пупкин - искомый текст, а Петя Лавочкин - текст для замены. В приведенной форме команда выполнит поиск и замену только первого вхождения искомого текста. Чтобы заменить текст по всему файлу, после последнего слэша (он обязателен в любом случае, без него sed не распознает конца заменяющего фрагмента) нужно указать флаг g (от global). Важно помнить, что если оставить заменяющее поле пустым, искомый текст будет просто удален.
По умолчанию sed выводит результаты своей работы на стандартный вывод, не внося изменений в файлы аргументы. Так где же здесь редактирование? Оно обеспечивается другой опцией - -i, указание которой внесет изменения непосредственно в обрабатываемый файл.В результате команда для замены, например, всех вхождений html на shtml во всех файлах текущего каталога будет выглядеть так:
$ sed -i -e 's/html/shtml' *
А что делать, если таким же образом нужно обработать файлы во всех вложенных подкаталогах? Придется вспомнить об универсальной команде find, описанной . В форме
$ find . -name * -exec sed -i -e 's/html/shtml' * {} \
она с успехом справится с этой задачей.
Я привел лишь элементарные примеры использования sed. На самом деле возможности его много шире - представление о их применении в реальных ситуациях можно получить из специальной статьи Сергея Майкова.
Истина - в командах
Нам уже приходилось говорить о командах, а некоторые даже использовать на практике. Потому что командный интерфейс (интерфейс командной строки, Command Line Interface, он же CLI) - это очередная вечная истина POSIX-мира, постижение которой позволит пользователю взаимодействовать с системой.
Интермедия: команды обработки текстов
В одной из речь шла о командах, которые манипулируют файлами как целыми, не затрагивая их содержания (и, в общем случае, от такового не зависящими). Ныне же речь пойдет окомандах, создающих и изменяющих внутреннее содержание файлов.
История команд
Возможности навигации и редактирования строки особенно ярко проявляются в сочетании с другой замечательной особенностью, предоставляемой командными оболочками - доступом к истории команд. То есть: раз введенная в строке команда не уходит в небытие после исполнения, а помещается в специальный буфер памяти (который, как и все в Unix'ах, именуется весьма незатейливо - буфер истории команд; однако смешивать его с экранным буфером консоли, о котором говорилось в предыдущей главе, не следует - это вещи совершенно разные). Откуда команда (со всеми ее опциями и аргументами) может быть извлечена для повторного использования. Или - для редактирования и исполнения в новой реинкарнации.
Буфер истории команд сохраняется в течении всего сеанса работы. Однако в большинстве случаев командные оболочки настраиваются так, что по выходе из сеанса буфер истории сохраняется в специальном файле в домашнем каталоге пользователя, и таким образом его содержимое оказывается доступным при следующем запуске шелла. Имя этого файла может быть различным в разных оболочках, но обычно включает компонент history (в bash - ~/.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-команды в обобщенном виде выглядит следующим образом:
$ 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 выступает не только внешняя команда, но и все относящиеся к ней опции.
В последнем примере имеется несколько символов, смысл которых может показаться непонятным. Надеюсь, он прояснится достаточно скоро - в параграфе о регулярных выражениях.
Командная строка
Основой командного интерфейса является командная строка, начинающаяся с приглашения для ввода (далее обозначается милым сердцу россиянина символом длинного зеленого друга - $, хотя вид приглашения может быть настроен в широких пределах). Это - среда, в которой задаются основные элементы командного интерфейса - командные директивы с их аргументами и опциями.
Командная директива (или просто команда) - основная единица, посредством которой пользователь взаимодействует с шеллом. Она образуется по определенным правилам, именуемым синтаксисом. Синтаксис командной директивы определяется, в первую очередь, языком, принятым в данной командной оболочке. Кроме того, некоторые команды (не очень многочисленные, но весьма употребимые) имеют собственный, нестандартный синтаксис.
Однако в целом базовые правила построения команд имеют много общего - по крайней мере, в POSIX-совместимом семействе. И именно эти базовые правила будут предметом данного раздела. Синтаксические особенности отдельных нестандартных команд будут оговариваться по ходу изложения.
Итак, командная директива образуется:
именем команды, однозначно определяющим ее назначение,
опциями, определяющими условия выполнения команды, и
аргументами - объектами, над которым осуществляются действия.
Очевидно, что имя команды является обязательным компонентом, тогда как опции и аргументы могут и отсутствовать (или подразумеваться в неявном виде по умолчанию).
Еще один непременный компонент командной директивы - это специальный невидимый символ конца строки: именно его ввод отправляет команду на исполнение. В обыденной жизни этот символ вводится нажатием и отпусканием клавиши Enter. Почему обычно и говорят: для исполнения команды нажмите клавишу Enter. Тот же эффект, как правило, достигается комбинацией клавиш Control+M. Конца командной строки, знаменующего исполнения команды, мы на экране не видим. Однако важно, что это - такой же символ, как и любой другой (хотя и имеющий специальное значение). Это знание нам понадобится, когда речь дойдет до специальных символов вообще (и даже немного раньше).
В подавляющем большинстве случаев опции (или их последовательности) задаются непосредственно за именем команды, а аргумент (или группа аргументов) команду завершает, хотя это правило имеет некоторые исключения. Вне зависимости от порядка опций и аргументов, принятых для данной команды, интерпретация их осуществляется слева направо.
Команды, опции и аргументы обязательно разделяются между собой пробелами. Кроме того, опции обычно предваряются (без пробела) символом дефиса или двойного дефиса. Впрочем, немногочисленные (но весьма употребимые) команды могут использоваться с опциями без всяких предваряющих символов.
Как уже говорилось, имя команды определяет выполняемые ею функции. Существуют команды, встроенные в оболочку, то есть не имеющие запускающих их исполняемых файлов, и команды внешние. В последнем случае имя команды однозначно указывает на имя исполняемого файла программы, выполняемой при отдаче соответствующей директивы. Часто встроенные и внешние команды одного назначения имеют одинаковые имена - в этом случае по некоторым причинам, которые станут ясными через раздел, обычно предпочтительно использование встроенных команд - впрочем, они и вызываются в первую очередь.
Некоторые команды могут выступать под несколькими именами. Это связано с тем, что исторически в различных Unix-системах команды, исполнявшие одинаковые функции, могли получать разные названия. В каждой конкретной системе обычно используется только одна из таких команд-дублеров. Но при этом имена дублирующих команд также могут присутствовать в системе - для совместимости. Не следует думать, что это две различные программы одного назначения: как правило, такая синонимичность команд реализуется посредством механизма ссылок или псевдонимов (alias), о которых речь пойдет позднее.
Иногда команда, вызванная через имя своего синонима, может отличаться по своей функциональности от самой же себя, вызванной под родным именем. В этом случае говорят о эмуляции одной команды другой. Типичный пример - командная оболочка /bin/bash в большинстве дистрибутивов Linux имеет своего дублера - /bin/sh; вызванная таким образом, она воспроизводят функциональность того самого пресловутого POSIX-шелла, о котором я недавно говорил как о явлении мифическом.
Для правильного применения команд, конечно же, нужно знать их имена и назначение. Однако нас никто не заставляет напрягать пальцы вводом имени команды полностью. Потому что нам на помощь приходит великий принцип автодополнения: для любой команды достаточно ввести первые несколько ее символов - и нажать клавишу табуляции (Tab). И, если введенных буковок достаточно для однозначной идентификации, полное имя команды волшебным образом возникнет в строке. Если же наш ввод допускает альтернативы продолжения имени - все они высветятся на экране (сразу или после повторного нажатия на табулятор), и из них можно будет выбрать подходящую.
Поясню на примере. Создание пустого файла выполняется командой touch. Чтобы ввести ее имя в строку, достаточно набрать первые три ее буквы - tou, - и клавишу Tab, остальные два символа будут добавлены автоматически. Если же мы из естественной человеческой лени ограничимся только двумя первыми символами, то после нажатия табулятора нам будет предложен список возможных дополнений:
$ to toc2cue toc2mp3 toe top touch
из которого мы и выберем подходящее. В данном случае достаточно набора еще одной буковки u и повторного нажатия на Tab. А вот если прибегнуть к табулятору в пустой командной строке - перед нами предстанет список имен всех команд, доступных в данной системе. Правда, перед этим обычно задается вопрос, а хотим ли мы созерцать эти 500-700 имен.
Большинство употребимых команд POSIX-систем - коротки и мнемонически прозрачны. И может показаться. что не такое уж это облегчение - заменить ввод двух-трех символов нажатием табулятора (а то еще и неоднократным). Однако, когда речь дойдет до аргументов команд - тут вся мощь автодополнения станет явной.
И еще маленькое отступление. Автодополнение - стандартная возможность bash и всех других командных оболочек, относимых к категории развитых. Но как раз в стандарте POSIX эта возможность не предусмотрена, и потому POSIX shell ее лишен. А в современных представителях семейства C-shell (tcsh) автодополнение реализуется несколько иначе.
Командные конструкции
Пора вернуться к генеральной линии моего рассказа - основам командного интерфейса. Надеюсь, из предшествующего изложения читателю стало ясно, что подавляющее большинство команд в 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-систем, заценить которое могут в полной мере только те, кто застал времена "черного 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, весьма поспособствуют виртуозному освоению любого из этих инструментов.
И второе - действие ключевых последовательностей, как правило. не зависит не только от типа терминала и физического устройства клавиатуры, но и от ее раскладки - при переключении на кириллицу они будут работать столь же справно, как и в латинице.
Опции
Как уже говорилось, указания имени достаточно для выполнения некоторых команд. Типичный пример - команда 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) - это и есть те самые атрибуты доступа, данные в символьной нотации, о которых речь шла в .
Опции команды именуются также флагами (реже ключами) или параметрами. Однозначной трактовки этих терминов нет. Однако обычно под флагами подразумеваются опции, не требующие указания значений.
Термин параметр же применяется к опции, такового требующей, и объединяет опцию и ее значение. Правда, мне встречалось определение параметра как совокупности опций и аргументов, но я буду придерживаться приведенных определений.
Опции определяют условия выполнения команды. Выше был приведен пример команды 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.
Особый смысл имеет символ удвоенного дефиса --, если после него не следует никакой опции: таким образом обозначается конец списка опций, и все последующие, отделенные пробелом, символы интерпретируются как аргументы (со временем я расскажу, когда это оказывается необходимым). Одинарный же дефис с последующим пробелом, напротив, подменяет аргументы команды: то есть в качестве таковых рассматривается стандартный ввод: знание этого нам потребуется, когда речь дойдет до командных конвейеров.
Переменные
Переменные играют для аргументов команд примерно такую же роль, что и псевдонимы - для команд. То есть избавляют от необходимости мрачного ввода повторяющихся последовательностей символов. Конечно, это - далеко не единственное (а может быть, и не главное) назначение переменных, однако на данном этапе для нас наиболее существенное.
Что такое переменная? Ответ просто - некоторое имя, которому присвоено некоторое значение. Не очень понятно? - Согласен. Но, возможно, станет яснее в дальнейшем.
Имена переменных в принципе могут быть любыми, хотя некоторые ограничения также существуют. Я уже вскользь упоминал о переменных в разделе про пользователей. В частности, там, помнится, фигурировали переменные 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.
Понятие о функциях
И уж совсем в заключение этой главы осталось сказать только пару слов о функциях командной оболочки. Это - такая же последовательность команд (или даже просто одиночная команда), как и сценарий, но - не (обязательно) вынесенная в отдельный исполняемый файл, а помещенная в тело другого скрипта. В коем она опознается по имени, и может быть выполнена неоднократно в ходе работы этого скрипта.
Главное отличие функции от сценария - в том, что она выполняется в том же процессе (и, соответственно, экземпляре шелла), что и заключающий сценарий. Тогда как для каждого скрипта, вызываемого из другого сценария, создается отдельный процесс, порождающий собственный экземпляр шелла. Это может быть важным, если в сценарии определяются некоторые переменные, которые имеют силу только в нем самом.
Функции не обязательно помещаются внутрь сценария - их можно собрать в некоторые отдельные файлы, которые именуются библиотеками функций и могут быть использованы по мере надобности.
Ранее на протяжении всего повествования неоднократно упоминались (и будут упоминаться впредь) системные библиотеки, в частности, главная библиотека glibc. Так вот, это - точно такие же сборники функций, правда, не командной оболочки, а языка Си, и, соответственно, хранящиеся не в виде текстовых файлов, а в бинарном, откомпилированном, виде.
Просмотр файлов
Однако прежде чем как-то манипулировать контентом файлов, желательно этот самый контент некоторым образом просмотреть. И тут можно вспомнить о команде cat, посредством которой мы некогда создавали файлы. Данная с именем файла в качестве аргумента, она выведет его содержимое на экран. Можно использовать и конструкцию перенаправления:
$ cat < filename
Не смотря на то, что в принципе это разные вещи, результат будет тот же - вывод содержимого файла на экран.
Недостаток команды cat как средства просмотра - невозможность перемещения по телу файла: выведя содержимое, она завершает свою работу. Конечно, "пролистывание" выведенного возможно, но - средствами системной консоли, а не самой команды.
Поэтому обычно для просмотра содержимого файлов используются специальные программы постраничного просмотра - т.н. pager'ы, очередной пример того, что передача этого термина исконно русским словом "пейджер" (а мне попадалось и такое) может создать совершенно превратное представление о сути дела.
В Unix-системах имеется две основные программы pager'а - more и less. Первая из них допускает только однонаправленный (вперед) просмотр и имеет слабые интерактивные возможности. Почему ныне и представляет лишь исторический интерес, так что о ней я говорить не буду. Тем более, что в современных свободных POSIX-системах она как таковая отсутствует: файл с именем /usr/bin/more, который можно обнаружить во FreeBSD и некоторых дистрибутивах Linux, на самом деле представляет собой жесткую или символическую ссылку на ту же самую программу, что и команда less. Хотя эта программа проявляет несколько различные свойства, в зависимости от того, какой из указанных команд она вызвана, функции ее от этого не меняются. Так что дальше я буду говорить только о команде less.
Самый простой способ вызова команды
$ less filename
после чего на экран выводится содержимое файла, указанного в качестве аргумента, по которому можно перемещаться в обоих направлениях, как построчно, так и постранично.
В нижней строке экрана можно видеть символ двоеточия - приглашения для ввода команд различного назначения. В частности, нажатие клавиши h выводит справку по использованию less, а клавиши q - выход из программы просмотра (или из просмотра справочной системы, если она была перед этим вызвана). Если команда была вызвана как more (это достигается еще и специальной опцией - less -m), вместо символа двоеточия в нижней строке будет выведено имя файла с указанием процента просмотра:
command.txt 3%
что, однако, не воспрещает и здесь давать ее встроенные команды - вводом символа двоеточия (:) и закрепленной за командой литеры (или их сочетания).
Большинство встроенных команд less предназначено для навигации по телу файла. Осуществляется она несколькими способами:
с помощью стандартных клавиш управления курсором: PageDown или Spacebar (вперед на один экран), PageUp (назад на один экран), Down или Enter (вперед на одну строку), Up (назад на одну строку), Right (на пол-экрана вправо), Left (на пол-экрана влево);
с помощью предопределенных клавишных комбинаций, сходных с управляющими клавиатурными последовательностями командных оболочек и таких текстовых редакторов, как emacs и joe (хотя и не всегда с ними совпадающими): Control+V (на один экран вперед), Escape-V (на один экран назад), Control+N (на одну строку вперед), Control+P (на одну строку назад);
с помощью фиксированных символьных клавиш, иногда подобных таковым командного режима редактора vi: z и w (вперед и назад на один экран), e и y (вперед и назад на одну строку, можно использовать также привычные по vi клавиши j и k, соответственно), d и u (вперед и назад на пол-экрана).
Последний способ интересен тем, что допускает численные аргументы перед символьной командой: так, нажатие 3e приведет к перемещению на три строки вперед, а 2w - на два экрана назад.
Помимо "плавной", так сказать, навигации, можно перемещаться по файлу и скачками (jumping): нажатие клавиши с символом g (или последовательности Escape-) позволяет переместиться к первой строке файла, клавиши G (регистр важен! дублирующий вариант - Escape->) - к последней его строке, клавиши p - к началу файла.
Кроме навигации, имеется и возможность двустороннего поиска - в направлении как конца, так и начала файла. Для поиска вперед требуется ввести символ прямого слэша (/) и за ним - искомое сочетание символов. Поиск в обратном направлении предваряется символом вопроса (?). В обоих случаях в шаблоне поиска можно использовать стандартные регулярные выражения *, ?, [список_символов] или [диапазон_символов]. Нажатие клавиши n (в нижнем регистре) приводит к повторному поиску в заданном ранее направлении, клавиши N (в верхнем регистре) - к поиску в направлении противоположном.
Управляющие комбинации команды less могут быть переопределены с помощью команды lesskey. Формат ее
$ lesskey -o output input
В качестве входных данных выступает простой текстовый файл (по умолчанию - ~/.lesskey, однако его следует создать самостоятельно), описывающий клавишные последовательности в следующем, например, виде:
#command \r forw-line \n forw-line ... k back-line ...
Выходные данные - создаваемый из текстового двоичный файл, который собственно и используется командой less. Стандартное для него имя - ~/.less.
Команда less допускает одновременный просмотр нескольких файлов. Для этого ее следует вызвать в форме
$ less file1 file2 ... file#
после чего между открытыми файлами можно переключаться посредством :n (к следующему файлу), :p (к предыдущему файлу), :x (к первому файлу). Путем нажатия :d текущий файл исключается из списка просмотра. Символ двоеточия во всех этих случаях вводится с клавиатуры в дополнение к приглашению на ввод команд.
Команда less имеет великое множество опций - описание их на странице экранной документации занимает более дюжины страниц, поэтому задерживаться на них я не буду. Следует заметить только, что опции эти могут использоваться не только в командоной строке при запуске less, но и интерактивно - после символа дефиса в приглашении ввода. Так, указав там -m, можно включить т.н. промежуточный формат приглашения (с отображением процентов просмотренного объема файла), а с помощью -M - длинный (more-подобный) формат, при котором в приглашении дополнительно указываются имя файла, его положение в списке загруженных файлов, просматриваемые ныне строки:
command.html (file 2 of 10) lines 1-29/1364 2%
Значение команд постраничного просмотра файлов еще и в том, что именно с их помощью осуществляется доступ к экранной документации (man-страницам). Команда
$ man cmd_name
как было описано в предыдущей интермедии, на самом деле вызывает определенный по умолчанию pager для просмотра соответствующего файла /usr/share/man/man#/cmd_name.gz. Какой именно - определяется переменной PAGER в пользовательских настройках.
Кроме команд постраничного просмотра, существуют команды для просмотра фрагментарного. Это - head и tail, выводящие на экран некоторую фиксированную порцию файла, указанного в качестве их аргумента, с начала или с конца, соответственно. По умолчанию эта порция для обеих команд составляет десять строк (включая пустые). Однако ее можно переопределитьg произвольным образом, указав опции -n [число_линий] или -c [количество_байт]. Например, команда
$ head -n 3 filename
выведет три первые строки файла filename, а команда
$ tail -c 100 filename
его последние 100 байт. При определении выводимого фрагмента в строках название опции (n) может быть опущено - достаточно числа после знака дефиса.
Существуют и средства просмотра компрессированных файлов. Для файлов, сжатых программой gzip, можно использовать команды zcat и zmore, для спрессованных командой bzip2 - команду bzcat. Использование их ничем не отличается от аналогов для несжатых файлов - в сущности, именно они и вызываются для обеспечения просмотра. В случае команды zmore, как нетрудно догадаться, на самом деле используется команда less (сама по себе она аналога для компрессированных файлов не имеет).
Псевдонимы
Вернемся на минуту к команде 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'
и так далее.
Когда я сказан о пользовании псевдонимами ныне, и присно, и вовек, - то был не совсем точен.Ныне, то есть в текущем сеансе пользователя - да, они работают. Однако после рестарта системы (или просто после выхода из данного экземпляра командной оболочки) они исчезнут без следа. Чтобы заданные псевдонимы увековечить, их нужно прописать в конфигурационном файле пользовательского шелла. Но этим мы займемся впоследствии - в . А пока обратимся к переменным.
Регулярные выражения
Не зря я, видно, вспомнил во вступлении к этой главе о семи смертных грехах. Потому что одному из этих грехов все пользователи-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'ивиста должны быть доведены до уровня рефлексов. Однако - лучше поздно, чем никогда, так что - благословясь, приступим.
Как как можно логадаться по прочтении предшествующих параграфов этой главы, команд в 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), то нашему взору предстанет все доступное изобилие команд.
Конечно, с некоторыми оговорками. Клавиша табуляции не извлекает волшебным образом имена команд откуда-то из загашников системы. Нет, она просто выводит список всех исполняемых файлов (то есть файлов, для которых установлен атрибут исполнения - см. ), расположенных в каталогах, перечисленных как значения переменной 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 и т.д. Назначение этих групп следующее:
man1 - команды и прикладные программы пользователя;
man2 - системные вызовы;
man3 - библиотечные функции;
man4 - драйверы устройств;
man5 - форматы файлов;
man6 - игры;
man7 - различные документы, не попадающие в другие группы (в том числе относящиеся к национальной поддержке);
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%).
Сценарии оболочки: первые представления
Наш затянувшийся разговор о командах и командном интерфейсе подходит к концу. Честно говоря, начиная этот раздел, я не думал, что он окажется таким длинным. Но это - тот самый случай, когда из песни слова не выкинешь. Напротив, очень многое осталось недосказанным или необъясненным. Что ж - тем больше поводов будет у нас возвращаться к теме команд вновь и вновь.
А в заключение этого раздела - еще немного терпения. Потому что было бы несправедливо не уделить чуть-чуть места тому, что придает командному интерфейсу 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 данного пользователя.
Сравнение, объединение и деление файлов
Следующая важная группа операций над контентом файлов - сравнение файлов по содержанию и различные формы объединения файлов и их фрагментов. Начнем со сравнения. Простейшая команда для этого - cmp в форме
$ cmp file1 fil2
производит построчное сравнение файлов, указанных как первый и второй аргументы (а более их и не предусмотрено, все указанное после второго аргумента игнорируется). В случае идентичности сравниваемых файлов не происходит ничего, кроме возврата приглашения командой строки. Если же между файлами имеются различия, выводится номер первого различающегося символа и номер строки, в которой он обнаружен:
file1 file2 differ: char 27, line 4
Это означает, что различия между файлами начинаются с 27-го от начала файла символа (включая пробелы, символы конца строк и т.д.), который имеет место быть в строке 4. С помощью опций -l и -z можно заставить команду cmp вывести номера всех различающихся символов в десятичном или шестнадцатеричном формате, соответственно.
Более информативный вывод обеспечивает команда diff. Она также осуществляет построчное сравнение двух файлов, но выводит список строк, в которых обнаружены отличия. Например, для двух файлов вида
$ less file1 line 1 line 2 line 3 line 4 line 5
и
$less file2 line 1 line 2 line 3 line 3a line 4 line 5
это будет выглядеть следующим образом:
$ diff file1 file2 3a4 > line 3a
Если различия будут выявлены более чем в одной строке, для каждого расхождения будет выведен аналогичный блок. Смысл его - в том, какие строки первого файла должны быть преобразованы, и как именно, для того, чтобы файлы стали идентичными. Первая линия блока вывода содержит номер строки первого файла, подлежащей преобразованию, номер соответствующей строки второго файла и обозначенное буквенным символом преобразование, во второй линии приведена собственно строка - предмет преобразования. Символы преобразования - следующие:
a (от append) указывает на строку, отсутствующую в первом файле, но присутствующую во втором;
c (от change) фиксирует строки с одинаковым номером, но разным содержанием;
d ( от delete) определяет строки, уникальные для первого файла.
То есть в данном примере для преобразования file1 в file2 в него после строки 3 должна быть вставлена строка 4 из второго файла, что символизирует вторая линия блока - > line 3a, где > означает строку из первого сравниваемого файла. Если же аргументы команды diff дать в обратном порядке, вывод ее будет выглядеть следующим образом:
$ diff file2 file1 4d3 < line 3a
показывающим, что для достижения идентичности из file2 должна быть удалена четвертая строка (< line 3a, где < означает строку из второго файла). Если же произвести сравнение file1 с file3, имеющим вид
$ less file3 line 1 line 2 line 3a line 4 line 5
то вывод команды
$ diff file1 file3 3c3 < line 3 --- > line 3a
будет означать необходимость замены третьей строки из file1 (символ ) на третью строку из file3 (символ >).
Такая форма вывода команды diff называется стандартной. С помощью опции -c можно задать т.н. контекстную форму вывода, при которой на экран направляется не только различающиеся строки, но и строки, их окружающие (то есть контекст, в котором они заключены):
diff -c file1 file2 *** file1 Sun May 12 11:44:44 2002 --- file2 Mon May 13 15:17:27 2002 *************** *** 1,5 **** --- 1,6 ---- line 1 line 2 line 3 + line 3a line 4 line 5
Количество строк контента задается опцией -C:
diff -C 1 file1 file2 ttyv1 *** file1 Sun May 12 11:44:44 2002 --- file2 Mon May 13 15:17:27 2002 *************** *** 3,4 **** --- 3,5 ---- line 3 + line 3a line 4
В этом примере значение опции -C (единица) предписывает вывод по одной строке контекстного окружения вокруг различающейся строки. Сами же различающиеся строки помечаются следующим образом: знаком - (минус, или дефис) - строки, подлежащие удалению из первого файла, знаком + (как в примере) - строки, которые должны быть добавлены, знаком ! - просто различающиеся строки.
Кроме контекстного формата, используется еще и вывод в унифицированном формате, что предписывается опцией -U [значение], в качестве значения указывается число строк.
В нем для обозначения изменяемых строк используются только символы + и -, а сам вывод чуть короче, чем при использовании контекстного формата.
С помощью многочисленных опций команды diff сравнение файлов может быть детализовано и конкретизировано. Так, опция -b предписывает игнорировать "пустые" символы пробелов и табуляции в конце строк, а опция -w - вообще "лишние" пробелы (и те, и другие обычно имеют случайное происхождение). При указании опции -B игнорируются пустые строки, то есть не содержащие никаких иных символов, кроме перевода каретки; строки с символами табуляции или пробела как пустые не рассматриваются, для их игнорирования требуется опция -w. Благодаря опции -i при сравнении не принимается во внимание различие регистров символов, а опция -I regexp определяет регулярные вырвжения, строки с которыми также игнорируются при сравнении.
В качестве аргументов команды diff (одного или обоих) могут выступать также каталоги. Если каталогом является только один из аргументов, для сравнения в нем отыскивается файл, одноименный второму аргументу. Если же оба аргумента суть каталоги, в них происходит сравнение всех однименных файлов в алфавитном порядке (вернее, в порядке ASCII-кода первого символа имени, разумеется). Благодаря опции -r сравнение файлов может осуществляться и во вложенных подкаталогах.
Вывод команды diff может быть перенаправлен в файл. Такие файлы различия именуются diff-файлами или, применительно к исходным текстам программ, патчами (patches), о которых будет сказано несколько позже. Именно с помощью таких патчей обычно распространяются изменения к программам (дополнения, исправления ошибок и т.д.).
В принципе, команда diff и придумана была именно для сравнения файлов исходников, над которыми ведут работу несколько (в пределе - неограниченное количество, как в случае с Linux) человек. Однако невозбранно и ее использование в мирных целях - то есть для сравнения просто повествовательных текстов. Единственное, что следует понимать при этом абсолютно ясно - то, что diff выполняет именно построчное сравнение.
То есть: сравнение последовательностей символов, ограниченных символами конце строки с обеих сторон. И, соответственно, непрерывная абзацная строка в стиле emacs и vi - совсем не то же самое, что строка, образуемая в редакторе joe на границе экрана. Впрочем, это - вопрос, к которому еще не раз придется возвращаться.
Как уже было отмечено, команда diff осуществляет сравнение двух файлов (или - попарное сравнение файлов из двух каталогов). Однако, поскольку Бог, как известно, любит троицу, есть и команда diff3, позволяющая сранить именно три файла, указываемые в качестве ее аргументов. По действию она не сильно отличается от двоичного аналога. И потому изучение ее особенностей предлагается в качестве самостоятельного упражнения приверженцам троичной идеологии.
Существуют и средства для сравнения сжатых файлов. Это - zcmp и zdiff. Подобно командам просмотра, ими просто вызываются соотвествтующие команды cmp и diff. И потому использование их не имеет никаких особенностей.
От вопроса сравнения файлов плавно перейдем к рассмотрению способов их объединения. Для этого существует немало команд, из которых по справедливости первой должна идти команда cat, поскольку именно сие есть ее титульная функция (cat - от concatenation, сиречь объединения). Ранее уже упоминалось, что она способна добавлять информацию со стандартного ввода в конец существующего файла. Однако дело этим не ограничивается. В форме
$cat file1 file2 ... file# > file_all
она создает новый файл, включающий в себя содержимое всех файлов-аргументов (и именно в том порядке, в каком они приведены в командной строке). Операция, казалось бы, нехитрая - однако представьте, сколько действий потребовалось бы в текстовом процессоре (например, в Word'е) для того, чтобы создать синтетический вариант из полутора десятков фрагментов, раскиданных по разным каталогам?
А вот команда patch выступает в качестве диалектической пары для команды diff, именно она вносит в файл те изменения, которые документируются последней.
Выглядит эта команда примерно так:
$patch file1 diff_file
в ответ на что последует нечто вроде следующего вывода:
Hmm... Looks like a normal diff to me... Patching file file1 using Plan A... Hunk #1 succeeded at 4. done
В результате исходная версия file1 будте сохранена под именем file1.orig, а сам он преобразован в соответствие с описанием diff-файла. Возможна и форма
patch < diff_file
В этом случае команда patch попытается сама определить имя файла-оригинала, и, если это ей не удастся, даст запрос на его ввод. Обращаю внимание на символ перенаправления ввода, поскольку если его опустить, имя dif-файла будет воспринято как первый аргумент команды (то есть имя файла-оригинала).
В качестве второго аргумента команды patch могут использоваться dif-файлы не только в стандартном, но и в контекстном или унифицированном формате. Это следует указать посредством опции -c или -u, соответственно.
Сочетание команд diff и patch очень широко используется при внесении изменений в исходные тексты программы. Так, если мы обратимся к коллекции портов FreeBSD, то в большинстве каталогов портированных программ можно обнаружить подкаталог files (например, /usr/ports/editors/joe-devel/files), содержимое которого как раз и есть те патчи, которые должны накладываться на исходные тексты программы для ее корректной сборки, установки и функционирования во FreeBSD. Сам же исходный текст при этом в неизменном, то есть распространяемом разработчиком, виде получается с его сайта (или любого другого сервера, в том числе и каталога distfiles ftp-сервера проекта FreeBSD.
Не менее часто, чем в слиянии, возникает и необходимость в разделении файлов на части. Цели этой служит команда split. Формат ее:
$ split [options] filename [prefix]
В результате исходный файл будет разбит на несколько отдельных файлов вида prefixaa, prefixab и так далее. Значение аргумента prefix по умолчанию - x (то есть без его указания итоговые файлы получат имена xaa, xab и т.д.).
Опции команды split задают размер выходных файлов - в байтах (опция -b) или количестве строк (опция -l).
Первой опцией в качестве единицы, кроме байтов, могут быть заданы также килобайты или мегабайты - добавлением после численного значения обозначения k или m, соответственно.
Команда split может использоваться для разбиения файлового архива на фрагменты, соответствующие размеру резервных носителей. Так, в форме
$ split -b 1474560 arch_name
она обеспечит разбиение архива на части, какждая из которых может быть записана на стандартную трехдюймовую дискету. А посредством
$ split -b 650m arch_name
архив можно подготовить к записи на носители CD-R/RW. Напомню, что именно командой split создаются файлы дистрибутива FreeBSD. Легко догадаться, что обратное слияние таких фрагментированных файлов можно выполнить командой cat.
Однако этим возможности BSD-реализации команды split не исчерпываются. С помощью опции -p файл может быть разделена на фрагменты, ограниченные строками, сорержащими текст, приведенный в качестве значения шаблона. Так, командой
$ split -p Глава filename chapter-
текст произвольной длинны будет легко разбит на файлы, соответствующие отдельным главам, которым будут присвоены имена chapter-aa и далее. В качестве значения опции -p могут использоваться тэги HTML или символы разметки TeX, а также регулярные выражения.
Linux-реализация команды split таким свойством не обладает. Однако взамен этому в Linux есть команда csplit, именно для разделения файла по шаблону и предназначенная.
Вступление
Конечно, само по себе манипулирование файлами (копирование, перемещение и т.д.) также подразумевает изменение содержания некоторых файлов, но только одного-единственного типа (а именно - каталогов), однако собственно внутренняя сущность обычных файлов при этом не изменяется. Предметом же настоящей интермедии будут штатные средства POSIX-систем, позволяющие в той или иной мере учитывать контент файлов и манипулировать им. Разумеется, манипулирование контентом возможно только для регулярных файлов. При этом многие их разновидности (бинарные файлы, файлы графических форматов и word-процессоров) требуют для изменения своего содержания специальных средств - а именно, компиляторов и прикладных программ, в которых они создавались. Однако здесь о них разговора не будет - ибо целью моей было продемонстрировать мощь обычных команд для решения многих пользовательских задач. Правда, на самом деле команды модификации контента действенны преимущественно для файлов текстовых.
Однако круг объектов таких команд не столь уж узок, как может показаться. Ведь именно в виде обычных текстовых файлов в ОС POSIX-семейства хранится масса общесистемной информации, исполняемых сценариев, баз данных атрибутов самых разных объектов. Далее - собственно нарративные тексты любого содержания: ведь чисто текстовый формат для них куда роднее, чем всякого рода *.doc и *rtf. Ну и никем не возбраняется использовать такие команды в отношении текстов с разметкой - HTML ли, XML, TeX или еще чего. Так что поле приложения рассматриваемых команд - достаточно обширно.
Введение в 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-семейства. И для понимания этих принципов необходимо представлять, как эти самые операции - загрузка и инициализация, - происходят.
О загрузке и загрузчиках
Начать изучение старта системы резонно с первого ее этапа - а именно, собственно загрузки. Как уже было сказано, управление этим этапам осуществляет специальная программа, которая по русски так и называется - загрузчик. хотя в английском для нее употребляется два термина - loader и boot manager (что, как мы увидим со временем, немного разные вещи, но сейчас это не принципиально).
На самом деле любой загрузчик включает в себя две или даже три относительно независимые части - даже если он распространяется в виде единого пакета, как Lilo или GRUB. Чтобы убедиться в этом, представим себе, как происходит запуск машины на "железном", так сказать, уровне (имеется ввиду - intel-совместимой персоналки, на иных архитектурах все обстоит несколько иначе).
Перво-наперво после включения питания запускается программа, прошитая в ПЗУ компьютера (BIOS). Она выполняет проверку железа, после чего отыскивает носитель, установленный в BIOS Setup в качестве первого загрузочного устройства (для определенности - винчестер), на нем - первый физический блок, содержащий так называемую главную загрузочную запись (MBR - Master Boot Record).
Содержимое MBR - это, во-первых, таблица дисковых разделов, тех самых четырех, в один из которых мы ранее установили DragonFly. А во-вторых - некий код, принимающий на себя управление от BIOS по окончании его работы. В стандартном MBR - том, что записан на "свежевкрученном" винчестере или восстанавливается после DOS-команды FDISK /mbr, - этот код только и может, что отыскать первый физический раздел диска (primary partition) и передать управление на его загрузочный сектор. Чего вполне достаточно для загрузки операционок вроде DOS или Windows 9X/ME с первого (или единственного) раздела. Но явно мало в любом другом случае - например, если на диске установлено несколько ОС, которые, естественно, не могут уместиться в одном разделе.
Поэтому в состав любого загрузчика должна входить программа, записываемая в MBR. Поскольку объем последнего - всего 512 Кбайт (размер физического дискового блока), из которых часть уже занята под таблицу разделов, особо богатых функций в эту программу не вместить.
Обычно она способна на то, чтобы опознать все задействованные (used) первичные разделы, вывести их список и позволить пользователю выбрать раздел для загрузки, после чего передать управление на загрузочный сектор выбранного раздела.
Подобно MBR, загрузочный сектор раздела (Boot Record - уже не Master!) содержит информацию о его разметке (Disk Label), зависящие от используемой в данной ОС ее схемы, и управляющий код, принимающий эстафету от программы, записанной в MBR. И этот код - вторая составная часть загрузчика. Правда, и ее возможности также не могут быть богатыми - ведь размер загрузочного сектора раздела составляет те же 512 Кбайт. И потому на нее возлагается одна-единственная функция - передать управление программе, лежащей за пределами загрузочного сектора. Которая, собственно, и должна опознать корневой раздел ОС и несомую им файловую систему, после чего, прямо или опосредованно, загрузить ее ядро.
Легко догадаться, что первые две составляющие загрузчика, в сущности, не имеют отношения ни к какой операционке, и не являются частями файловой системы вообще. А вот с третьей - возможны варианты. Она может входить в файловую иерархию загружаемой ОС, как Lilo, или представлять собой нечто вроде самостоятельной мини-ОС, как GRUB (не случайно его настоятельно рекомендуют устанавливать в собственный дисковый раздел, не монтируемый по умолчанию к корню любой из загружаемых им операционок).
Стадиальность загрузки системы отчетливо выражена в BSD Loader - программе, используемой для загрузки операционок этого семейства, но могущей столь же успешно использоваться в качестве мультисистемного загрузчика. Однако здесь о ней речи не будет - этой теме посвящена самостоятельная статья из цикла про DragonFly.
Особенности BSD-стиля
Начнем с чистого BSD-стиля, как более простого, прямолинейного и потому более доступного пониманию начинающего пользователя (да и, по моему глубокому убеждению, более ему подходящего). Начать с того, что он предусматривает всего два режима загрузки - однопользовательский, предназначенный для аварийно-восстановительных работ и решения некоторых задач администрирования, и многопользовательский, при котором осуществляется вся нормальная деятельность пользователя.
В однопользовательском режиме загрузка происходит а) при выборе соответствующего пункта (Boot in single user mode) в меню начального загрузчика, б) при задании команды boot -s в командной строке загрузчика (после выбора пункта его меню Escape to loader prompt), и в) при обнаружении серьезных (неустранимых автоматически) нарушений целостности файловой системы в ходе ее проверки на первой стадии инициализации).
В любом случае при загрузке в однопользовательском режиме не монтируется ни одна файловая система из /etc/fstab, кроме корневой - да и та остается лишь в режиме read only, в котором она была смонтирована при старте системы, не отрабатывается ни один сценарий инициализации, и не активизируется ни один виртуальный терминал, кроме первого, исполняющего функции системной консоли, авторизация на которой по умолчанию - беспарольная, с автоматическим получением прерогатив суперпользователя (а зарегистрироваться от лица кого-либо другого, понятное дело, и невозможно). Очевидно, что никакая нормальная работа при этом невозможна, однопользовательский режим предназначен почти исключительно для аварийно-спасательных работ и потому подробнее будет рассмотрен в соответствующей интермедии.
При загрузке в многопользовательском режиме (а она осуществляется по умолчанию при нормальном включении машины или ее перезагрузке) все стадии инициации проходятся по полной программе: монтируются предназначенные к тому файловые системы из файла /etc/fstab (а корень ее ремонтируется в режиме чтения/записи), отрабатываются определённые стартовые скрипты (где и кем определенные - скоро увидим), и активизируются все описанные в файле /etc/ttys виртуальные терминалы (вплоть до графического приглашения к авторизации, если таковое определено).
Авторизация возможна как для администратора, так и для любого пользователя, но о беспарольном входе придется забыть. Короче говоря, идет нормальная цивилизованная работа...
Между однопользовательским и многопользовательским режимами не лежит непреодолимой пропасти: переход из одного режима в другой возможен не только при рестарте машины, но и в ходе одного сеанса. Для немедленного перехода в однопользовательский режим служит команда
$ dhutdown now
Возврат обратно в многопользовательский режим происходит по команде
$ exit
Такой способ широко применяется для реинициализации системы без полной ее перезагрузки после изменения каких-либо конфигурационных параметров - он протекает гораздо быстрее последней. Нужно только помнить, что при этом не все сервисы и демоны обязаны работать правильно.
Загрузка в многопользовательском режиме - и это отличительная особенность BSD-стиля, - потенциально влечет за собой доступность для запуска абсолютно всех системных служб и демонов: инициализирующие их сценарии (располагающиеся в каталоге /etc/rc.d) теоретически могут быть запущены из главного стартового сценария - файла /etc/rc. А вот какие из них будут запущены реально - определяется опциями его конфигурационного файла - /etc/rc.conf.
При установке системы, использующей BSD-стиль инициализации, в каталог /etc на диске записывается некий умолчальный /etc/rc.conf, строки имеют вид
servicename_enable="значение"
или
переменная="значение"
Значение строк первого вида = "YES" или "NO". Легко догадаться, что они разрешают (или запрещают) запуск поименованного сервиса посредством соответствующего ему (и, как правило, одноименного) сценария из каталога /etc/rc.d (для определенности я рассматриваю случай FreeBSD и DragonFlyBSD - стартовые схемы их практически идентичны, - но нечто подобное будет и в NetBSD, и в OpenBSD). Например, строка
moused_enable="YES"
разрешает запуск службы консольной мыши посредством скрипта /etc/rc.d/moused.
Значения же строк второго вида - это параметры, передаваемые командам, входящим в скрипты инициализации. Так, строки
moused_type="auto" moused_port="/dev/ums0"
определяют что значения опций -t (автоматическое определение протокола) и -p (имя файла мышиного устройства - в данном случае с USB-интерфейсом) для команды /usr/sbin/moused, запускаемой из сценария /etc/rc.d/moused.
Как правило - и это тоже традиция BSD-систем, - по умолчанию в файле /etc/rc.conf разрешен запуск лишь минимально необходимого для начала работы количества системных служб. Большая же их часть обычно запрещена - или явным образом, указанием значения "NO", или по умолчанию а откуда они берутся - мы скоро увидим). Так что включение необходимых пользователю демонов (например, той же консольной мыши) - дело рук самого пользователя.
Откуда берутся стартовые умолчания? А берутся они из файла /etc/defaults/rc.conf, в котором описаны всевозможные (и все возможные) стартовые сервисы и их параметры. Файл этот не предназначен для прямого редактирования (хотя оно и не запрещено атрибутами его доступа). Вместо этого полагается отыскать в нем строки, относящиеся к нужным сервисам, перенести их в /etc/rc.conf и разрешить их запуск (или, напротив, запретить, если оный разрешен по умолчанию, но в данном случае не нужен). Уточняющие опции сервисов также берутся из /etc/defaults/rc.conf, переносятся в /etc/rc.conf и им приписываются нужные значения.
В общем виде это делается, например, так: в одной виртуальной консоли (на которой нужно зарегистрироваться как root или получить его права командой su) в текстовом редакторе открывается файл /etc/rc.conf, в другой (на ней можно авторизоваться и обычным пользователем) дается команда
$ less /etc/defaults/rc.conf
И нужные строки из последнего просто переносятся в первый, где и модифицируются должным образом. Не вредно при этом задействовать и третью пользовательскую консоль - для чтения man (5) rc.conf. Как все это выглядит на практике - мы увидим в ближайшей интермедии.
Что же касается заключительной стадии BSD-инициации - процесса получения терминала, - то она контролируется записями в файле /etc/ttys, о котором уже говорилось в интермедии 13. Наличие специального файла для конфигурирования виртуальных терминалов - одно из важных отличий BSD-систем: в Linux, вне зависимости от стиля сценариев инициализации, принятых в конкретном дистрибутиве, настройка виртуальных терминалов описывается в общем конфиге процесса init.
Как уже говорилось, сценарии, отвечающие за запуск стартовых сервисов, находятся в каталоге /etc/rc.d (по крайней мере в современных версиях FreeBSD, а также в DragonFlyBSD это именно так). Большинство запускаемых ими программ - это так называемые демоны (daemon - Disk And Execution MONitor), нечто вроде резидентных программ, работающих в фоновом режиме в ожидании запроса на исполнение их функции (печати, отправки почты, обращения к ftp- или http-серверу, и так далее). В соответствие с этим запускающие их сценарии устроены по принципу start - stop. И если при инициации системы выполняется первая функция, то при ее останове, как легко догадаться, вторая.
Ход процесса останова системы определяется сценарием /etc/rc.shutdown. Назначение его - выполнить функцию stop в сценариях всех сервисов, запущенных из скрипта /etc/rc в соответствие с описанием, данным в /etc/rc.conf и /etc/defaults/rc.conf.
Особенности загрузчиков Lilo и GRUB
К тому же BSD Loader - далеко не самый распространенный из мультисистемных загрузчиков в свободном POSIX-мире. Многие пользователи Linux применяют в этом качестве Lilo (Linux Loader). Те же, кто по долгу или прихоти использует множество (более двух) операционных систем, да еще и меняют их, как перчатки, обычно отдают предпочтение загрузчику GRUB ( GRand Unified Bootloader).
Загрузчик Lilo и его настройка - предмет подробного описания в любой толстой книге про Linux. Поэтому скажу про него лишь пару строк. Это - такой же "цепочечный" загрузчик, как и BSD Loader. То есть в общем случае он записывается в MBR загрузочного диска, откуда при старте машины опознает дисковые разделы - как первичные, так и расширенные. И если раздел несет на себе метку Linux native - загрузит с него образ ядра Linux (изначально Lilo для этого и предназначался, его функции мультисистемного загрузчика вторичны). Если же тип раздела - иной, Lilo в состоянии "по цепочке" передать управление на его загрузочный сектор, и дальнейшее будет определяться тем кодом, что записан в последнем.
Тип файловой системы для Lilo по большому счету безразличен - то есть он способен загрузить ядро Linux с любой из ее многочисленных нативных файловых систем. Однако до недавнего времени он не мог выполнить эту операцию с файловой системы ReiserFS в случае, если в ней использовался так называемый tailing (или упаковка хвостов - см. ). Хотя ныне это, как-будто бы изжито, при размещении ReiserFS на корневом разделе обычно все равно рекомендуется выделять специальный раздел /boot с файловой системой Ext2fs - под ядро и служебные его файлы, для страховки. Ведь, хотя использование тайлинга можно отключить, он автоматически восстанавливается при реинсталляции ядра на файловой системе, несущей оное.
Для разделов "чуждых" операционок тип их файловой системы для Lilo безразличен тем более - ведь, как уже было сказано, он просто передает управление на их загрузочные сектора. И поэтому, если озаботиться тем, чтобы в загрузочном секторе BSD-слайса был записан код boot1 из BSD Loader, то Lilo благополучно справится с загрузкой любой ОС этого семейства.
А возможность записи соответствующего кода при установке BSD-систем предоставляется. Точно таким же способом - по "цепочке" - Lilo может обеспечить загрузку DOS, Windows 3.X/9X/ME и Windows NT/2000/XP.
Каждая ОС, предусмотренная для "загрузки" через Lilo (вы ведь понимаете, почему теперь я взял это слово в кавычки? - именно, потому что в полном смысле слова оно применимо только к загрузке Linux), представляет собой один из вариантов выбора в меню этой программы. Важно, что таким вариантом не обязательно отдельная операционка, - это могут быть разные дистрибутивы Linux, лежащие на собственных разделах, и разные версии ядра одной и той же Linux-системы, и просто разные сборки ядра одной и той же версии. Поэтому Lilo очень часто применяют для тестирования ядер этой системы, скомпилированных с разными опциями, или ядер разрабатываемой ветки.
Варианты загрузки через Lilo описываются в его специальном конфигурационном файле - /etc/lilo.conf. Это простой текстовый файл, доступный для изменения в текстовом редакторе (при наличии полномочий суперпользователя, разумеется). Он содержит несколько секций - общую, описывающую условия загрузки в целом и отдельные - описание каждого варианта загрузки.
Общая секция в виде отдельных строк содержит:
имя устройства, с которого выполняется запуск Lilo (именно Lilo, а не Linux или иной ОС), для случая первого диска это будет как boot=/dev/hda;
время ожидания выбора вариантов загрузки - timeout=##, где ## - время в миллисекундах;
имя варианта, загружаемого по умолчанию, которое должно соответствовать тому, что далее будет указано в секции этого варианта;
указание на режим - нынче это, как правило, строка вида lba32.
Содержание отдельных секций различается в зависимости от того, предназначен этот вариант для загрузки Linux или иной ОС. В общем случае обязательными здесь будут две строки. Первая - это метка варианта (label) - его уникальный идентификатор в виде произвольной, но, желательно, мнемонически прозрачной последовательности символов, например: linux, freebsd, windows.
Вторая же - имя устройства, несущего загружаемую ОС или его корневую систему (вроде /dev/hda1, /dev/hda5, и так далее).
Секция варианта, загружающего Linux, кроме обязательного идентификатора, должна включать:
имя файла, содержащего образ ядра, с указанием пути относительно корневого каталога, заданного в одной из следующих строк, что обычно выглядит как image=/boot/bzImage или image=/boot/vmlinuz;
имя устройства, несущего корневую файловую систему, для случая первого раздела на первом диске это будет root=/dev/discs/disc0/part1 при использовании файловой системы устройств или /dev/hda1 - без оной (или при использовании udev - см. Интермедию 12);
указание монтировать корневую файловую систему при загрузке ядра в режиме "только для чтения" read-only; это не значит, что она станет недоступной для изменений - в процессе инициализации файловые системы будут перемонтированы в соответствие с их описанием в /etc/fstab.
Кроме того, в Linux-секции /etc/lilo.conf вносятся строки, определяющие видеорежим при загрузке (например, vga=791 - режим графической консоли с разрешением 1024x768) и строки, передающие ядру некоторые параметры, вроде append="devfs=nomount" (запрет на монтирование файловой системы устройств, требующийся при задействовании механизма udev), или append="ide-scsi" (включение эмуляции протокола SCSI через IDE-шину).
Секции для не-Linux вариантов, как правило, не содержат ничего, кроме имени устройства несущего раздела и метки варианта:
other=/dev/discs/disc0/part1 label=dos
Lilo может использоваться и исключительно для загрузки единичной Linux-системы в сочетании с любым мультисистемным загрузчиком, отвечающим за загрузку систем прочих (BSD Loader, GRUB, вплоть до NT Loader). В этом случае в общей секции /etc/lilo.conf в качестве загрузочного устройства следует указать имя корневого раздела Linux-системы - ведь тогда Lilo будет стартовать уже с его загрузочного сектора (а не с MBR диска):
boot=/dev/hda1
В общей секции же секции можно раз навсегда определить имя устройства корневой файловой системы.
А далее возможны и отдельные секции для вариантов загрузки, но они будут описывать уже не самостоятельные ОС, а разные версии или сборки ядра, возможно - видеорежимы или параметры ядра.
Как уже было сказано, Lilo напрямую с файловыми системами не работает - даже файловыми системами Linux. Конфиг же этого загрузчика лежит в каталоге /etc, представляющем собой часть файлового древа инсталлированной Linux-системы, которая при старте машины еще не загружена. Как же загрузчик узнает о собственной конфигурации?
Очень просто, о существовании собственного конфига в момент запуска Lilo и не подозревает. Необходимые данные для его опознания прошиты в виде бинарного кода в загрузочном секторе - диска или раздела. И делается это одноименной командой - /sbin/lilo. При запуске она обращается к файлу /etc/lilo.conf, исходя из значения строки
boot=/dev/имя_устройства
в общей его секции, определяет, куда нужно записать данные - в MBR диска или загрузочный сектор раздела, и выполняет запись вариантов загрузки, завершаемую сообщением
Added имя_рек
где имя_рек - метка (label) добавленного или модифицированного пункта меню.
Из чего становится очевидным, что каждое изменение конфигурации загрузчика должно сопровождаться его переустановкой - перезапуском программы /sbin/lilo.
Сказанным в предыдущих абзацах определяются и два принципиальных ограничения на использование Lilo вообще. Первое вытекает из положения конфига этого загрузчика внутри файловой системы Linux. Так что для изменения его конфигурации из другой операционной системы необходимо иметь доступ к той файловой системе, на которой лежит корень инсталлированного Linux'а, причем в режиме записи. И если примонтировать Ext2fs/Ext3fs к файловой системе FreeBSD или DragonFlyBSD в режиме read/write можно без проблем (ныне это не потребует даже пересборки ядра и той, и другой ОС), то доступ из любой ОС BSD-семейства к разделам с ReiserFS или XFS в настоящее время невозможен (и неизвестно, будет ли возможен в обозримом будущем).
Конечно, можно взять за правило размещать корневую систему Linux только на разделе с Ext2fs/Ext3fs.
Однако второе ограничение - необходимость перезапуска /sbin/lilo, - этим не обходится, ведь эта программа предназначена для работы в родной ОС, сиречь Linux (интересно, а пробовал ли кто-нибудь запустить /sbin/lilo под FreeBSD в режиме Linux Compability?). И, соответственно, непременное условие для переконфигурирования Lilo - возможность запуска Linux в каком-никаком виде, хотя бы - с rescue-дискеты или LiveCD.
Есть и третье ограничение, не столь важное: относительно слабые (по сравнению с GRUB) интерактивные возможности. Конечно, Lilo позволяет в режиме командной строки вмешиваться руками в ход загрузки - но только загрузки Linux же (например, передавать параметры ядру). Да и то - в ограниченном объеме. Возможности же вмешательства в ход загрузки чужой ОС вообще заканчиваются в момент выбора соответствующего ей варианта.
Раз уж речь зашла об ограничениях Lilo, то, пользуясь случаем, подчеркну: прочие ограничения, на которые можно нат олкнуться в литературе, как то: невозможность загрузить ядро Linux с области диска, лежащей за пределами первых 1024 его цилиндров, или с логического раздела в разделе Extended DOS, - давно потеряли силу. И пользователь любого современного дистрибутива о них может смело забыть.
Так что основная сфера применения Lilo - это работа преимущественно (или исключительно) в Linux, при эпизодическом использовании какой-либо другой ОС. Причем рискну предположить, что под другой ОС будет выступать, скорее всего, какая-либо из версий Windows. Экспериментирование же с многочисленными операционками разных семейств - это, по моему мнению, прерогатива GRUB.
Как и Lilo, GRUB неоднократно был предметом описания в различных (преимущественно онлайновых) документах. Наиболее полное (из числа мне известных) содержится в цикле статей Владимира Попова, который можно найти здесь: http://unix.ginras.ru/linux/base011.html. К которой я отсылаю любознательного читателя, затронув ниже лишь основные особенности и возможности этой программы.
Если Lilo, как и следует из его названия, создавался в первую очередь для загрузки Linux при сохранении возможности старта Windows, то целью разработчиков GRUB изначально было создание универсального мультизагрузчика, максимально независимого от любой операционной системы.
История его началась в 1995 году, когда операционная система GNU Hurd (микроядерная ОС светлого будущего, столь же отдаленного, как и коммунизм в мировом масштабе) достигла той степени развития, что могла уже быть загружена. И встал вопрос - каким же образом это сделать, так как существовавшие тогда загрузчики оказались на это неспособными.
Конечно, с точки зрения идеологии GNU, логично было бы изобрести для этой цели собственный (еще один) загрузчик, в полной мере отвечающий идеалам свободы и демократии. Однако, к счастью для всего прогрессивного человечества, был избран иной путь: Multiboot Specification - универсальный метод загрузки ядер, этой самой спецификации соответствующих. Причем сама спецификация бралась отнюдь не с потолка - в ее основу легли особенности существующих ядер Linux и BSD-систем. Ведь, как я уже говорил, файлы их образов самодостаточны для запуска и несут в себе всю необходимую информацию - и дело упирается только в то, что в обычной ситуации они расположены на носителе с файловой системой, поддержка которых начинается только после загрузки соответствующего ядра. Так что суть универсальности предложенного метода и заключалась в том, чтобы загрузчик получил средства доступа к файловой системе.
Зримым же, действительно универсальным, воплощением Multiboot Specification и стал мультисистемный загрузчик GRUB. Собственно, единственным его ограничением для полноценного использования (ниже станет ясно, что "неполноценное" использование GRUB вообще ничем не ограничено) была совместимость с файловой системой загружаемой ОС. Ибо, в отличие от BSD Loader и Lilo, GRUB способен работать с очень многими файловыми системами напрямую, без всякого использования ресурсов соответствующей операционки - "монтировать", читать с них данные, подвергать их редактированию и записывать изменения. Что превращает его в своеобразную мини-ОС, имеющую к тому же свой собственный, шелл-подобный, интерфейс пользователя.
Можно выделить две степени совместимости GRUB с файловыми системами - полную и частичную.
Полностью совместимые файловые системы - это те, что способны нести GRUB сам по себе. И в их числе - абсолютно все нативные файловые системы Linux (включая JFS, имевшие некогда место проблемы с "монтированием" ReiserFS, насколько я знаю, в прошлом), Minix и FAT. То есть загрузчик GRUB может стартовать с любого носителя, отформатированного соответствующим образом - дискового раздела, дискеты Linux (на которых применяется файловая система Minix) или DOS (с файловой системой FAT). Хотя по причинам, которые скоро станут понятны, разработчики рекомендуют размещать GRUB на самостоятельном разделе с файловой системой Ext2fs.
Частично совместимые с GRUB файловые системы сами по себе нести его не могут. Но после своего старта он способен их идентифицировать, прочитать и загрузить с них ядро, если оно отвечает Multiboot Specification. И в эту группу попадают практически все файловые системы свободных операционок, внутренний формат которых общедоступен - кроме Linux, тут мы видим также все варианты FFS и UFS, используемых BSD-семейством. Если же какая-то из таких файловых систем (как правило, из числа только что созданных) кажется несовместимой с текущей версией GRUB - это исключительно временное явление. Потому что для любой открытой файловой системы нет никаких препятствий получить свою поддержку в GRUB.
Приведу пример: с появлением 5-й ветки FreeBSD она обрела новую нативную файловую систему, UFS2, несколько отличную от прототипа - UFS просто. В результате единовременная версия GRUB (0.93) оказалась неспособной работать с ней напрямую (ниже я покажу, что это все равно было обходимо). Однако уже для версии 0.94 появился патч поддержка UFS2, а с версии 0.95 GRUB поддерживает эту файловую систему, что называется, "из тарбалла".
Можно видеть, что в списке частично совместимых с GRUB файловых систем нет тех, что созданы Самой Великой Софтверной компанией, в частности - всех вариантов NTFS. Что понятно - ведь их внутреннее устройство - тайна за семью печатями.
Однако мир свободного софта в очередной раз подтверждает справедливость утверждения, что на самое хитрое ухо (вариант для дам) всегда найдется болт с левой резьбой. Существует он и здесь: операционки, файловые системы которых для GRUB недоступны, могут быть загружены "по цепочке" - передачей управления на загрузочный сектор соответствующего раздела. Тот же способ можно применить и для загрузки свободных ОС, файловые системы которых временно не поддерживаются на моей памяти таковыми бывали периодически некоторые версии OpenBSD и FreeBSD 5-й ветви. К слову сказать - даже "ухо с закоулками" (несовместимость ядра с Multiboot Specification, имеющая место для DOS/Windows9X/ME) не оказывается помехой для винта от GRUB. И указанные операционки могут быть столь же успешно загружены "цепочечным" методом.
Таким образом, универсализм GRUB в рабочем (то есть установленном и настроенном) состоянии сомнений не вызывает. Однако с точки зрения установки и настройки он предстает столь же независимым от любой ОС. Ибо устанавливается он с самодостаточной загрузочной дискеты. А с недавнего времени, благодаря усилиям Владимира Попова, это можно проделать и с мультизагрузочного LiveCD (который можно скачать отсюда). То есть: наличия инсталляции какой-либо ОС (или дистрибутива Linux не требуется).
Что же касается конфигурирования GRUB, то, если следовать приведенным ранее рекомендациям разработчиков (установке GRUB на отдельный дисковый раздел с файловой системой Ext2fs - и резоны к тому сейчас прояснятся), то и тут никаких сложностей не предвидится ни в одной из свободных ОС. В Linux этот раздел монтируется как каталог /boot, куда, вместе с файлами собственно загрузчика (в подкаталоге /boot/grub) помещаются также файлы ядра (/boot/vmlinuz и подобные ему). Причем разработчики опять же рекомендуют монтировать /boot не автоматически при старте Linux - после ее загрузки никакой необходимости к этому каталогу в обычных условиях нет), а руками - по мере необходимости (установки нового ядра или реконфигурации GRUB).
За настройку загрузки GRUB отвечает специальный файл, доступный из Linux как /boot/grub/menu.lst. Это простой текстовый файл, который можно модифицировать в любом редакторе. Причем, что ценно, предварительно проверив работоспособность вносимых изменений в интерактивном режиме. Описывать его формат в деталях не буду, он очень прост и станет ясным и приводимого в заключении примера.
Так что для конфигурирования же GRUB из какой-либо иной ОС требуется только возможность чтения раздела с Ext2fs и записи в него. А, как уже говорилось, получить такую возможность из FreeBSD и DragonFlyBSD ныне никакого труда не составит (да и в случае Net- или OpenBSD в худшем случае потребуется только пересборка их ядер). Так что рекомендация разработчиков о выборе файловой системы загрузочного раздела становится понятной - прочие файловые системы Linux BSD-семейством не поддерживаются, а FAT... ну он FAT и есть, всерьез говорить о нем не стоит.
Таковы основные особенности (и возможности) GRUB. О деталях его установки, настройки и интерактивной работы в ходе загрузки можно было бы написать еще много. Однако это уже проделано Владимиром Поповым, и повторяться я не буду. Памятуя и о прекрасной штатной документации этой программы, правда, в нелюбимом мною формате (info grub, стандартный man grub содержит лишь краткие о ней сведения). Так что в качестве завершающего штриха просто приведу прокомментированный (строки комментариев, как обычно, отмечены символом #) пример своего конфига, который долгое время верой и правдой служил мне при загрузке Archlinux.
# Config file for GRUB - The GNU GRand Unified Bootloader # /boot/grub/menu.lst
# Общая конфигурация загрузчика timeout 5 # Время ожидания выбора загружаемой ОС в секундах default 0 # ОС, загружаемая по умолчанию # (в данном случае Linux) color light-blue/black light-cyan/blue # Цветовая гамма меню (мне такая нравится)
# Секция, отвечающая за загрузку Archlinux # (0) Arch Linux title Arch Linux [/boot/vmlinuz] # Идентификатор ОС # он же - пункт меню загрузчика root (hd0,1) # Устройство, несущее корневую файловую систему: # 2-й раздел 1-го диска в нотации GRUB # В нотации Linux ему соответствует устройство # /dev/discs/disc0/part2 в следующей строке kernel (hd0,0)/vmlinuz root=/dev/discs/disc0/part2 ro # где # (hd0,0)/vmlinuz - положение файла образа ядра # на загрузочном устройстве в нотации GRUB - # 1-м разделе 1-го диска # root=/dev/discs/disc0/part2 # имя устройства с корневой файловой системой # уже в нотации Linux (при использовании devfs) # ro - предписание монтировать его в режиме read-only
А в ближайшей же интермедии я остановлюсь на вопросе, недостаточно освещенном в источниках - загрузке с помощью GRUB систем BSD-клана (в том числе и при отсутствии доступа к их файловой системе).
Стили инициализации
Мое описание инициализации POSIX-системы поневоле получилось чрезвычайно обобщенным. Попробую перейти к конкретике, для чего нам потребуется представление о стилях... нет, не работы, как говорил товарищ Мао, а инициации.
Стилей инициации, опять же, вопреки председателю КПК, не три, а два: BSD-стиль, повсеместно принятый в одноименных системах, и стиль System V, используемый в большинстве распространенных дистрибутивов Linux. Впрочем, Великий Кормчий не так уж и не прав. Потому что в некоторых дистрибутивах Linux, при сохранении родовых пятен System V (таких, как уровни выполнения, о которых будет говориться далее), применяются BSD-подобные стартовые сценарии, и это вполне можно считать третьим, промежуточным, стилем.
Это - размежевание лишь в первом приближении. Потому что сценарии инициализации в стиле System V - это та сфера, в которой майнтайнеры отдельных дистрибутивов Linux в своем стремлении к оригинальности оттягиваются по полной программе. Инициация в BSD-стиле, что в одноименных системах, что в дистрибутивах Linux, подвержена этой тенденции существенно в меньшей степени. Опять перефразируем нашего великого автора: пользователи всех BSD-схем счастливы одинаково, пользователи схем загрузки SysV - несчастливы по своему. Впрочем, приверженцы последнего стиля инициации, скорее всего, поменяли бы мои определения местами...
Стили System V
Основу инициализации в стиле System V, в более или менее чистой форме принятой в Linux, составляет понятие runlevels. Это один из тех терминов, любой русский перевод которого способен только сбить с толку начинающего пользователя. Потому что часто используемые переводы типа уровни запуска или уровни загрузки создают впечатление, что система в процессе инициализации последовательно проходит некие, соответствующие им, стадии. На самом деле, как мы видели, стадии инициализации действительно имеют место быть - вот только к runlevels они имеют отношение весьма косвенное.
Понять, что же такое runlevels в System V проще всего в сравнении с BSD-инициализацией. В которой, как мы только что видели, существует два режима - однопользовательский, при котором не исполняются никакие стартовые сценарии, и многопользовательский, при котором исполняются все сценарии, разрешенные в главном конфиге /etc/rc.conf и умолчальном конфиге /etc/defaults/rc.conf. Так вот, runlevels - это нечто вроде таких же режимов, только их - несколько больше, и каждому поставлен в соответствие набор исполняемых при его вызове сценариев.
Вызов runlevels, то есть метасценария для группы сценариев, осуществляется при инициализации системы первым ее пользовательским процессом - процессом init. А сами эти метасценарии описаны в главном конфигурационном файле этого процесса - /etc/inittab. Конфиг этот описывает весь ход процесса init - от проверки файловых систем до получения терминала. И является непременным атрибутом всех (по крайней мере, известных мне) Linux-систем, отличающим их от систем BSD-клана (где, как мы видели, единого конфигурационного файла для процесса init нет).
Теоретически процесс init предусматривает вызов семи runlevels - от нулевого до шестого; за тремя из них зафиксированы метасценарии определенного назначения, использование остальных отдано на откуп создателям дистрибутивов (и, как я уже говорил, они используют это на всю катушку). зарезервированы следующие runlevels:
0 - halt, то есть останов системы;
1 - single user mode, сиречь однопользовательский режим;
6 - reboot (перезагрузка системы).
Что в очередной раз показывает неадекватность из приведенных выше переводов: русскоязычному пользователю, не совсем лишенному элементов логического мышления, трудно понять, почему останов системы отнесен у ровням ее запуска (хотя я вполне допускаю, что у пользователя, родной язык которого - английский, слово runlevel вызывает совсем другие, "правильные" ассоциации). Хотя почему останов и перезагрузка системы вообще выступают в одном ряду с режимами ее запуска - понять вообще невозможно, даже отвлекаясь от перевода, - но это уже фирменный стиль System V.
Опять, однако, отвлекся - вернусь к прочим runlevels. Их использование - свободно. Как правило, один из свободных номеров (обычно 3 или 4) задействуется под нормальный многопользовательский режим работы, другой же (4 или 5) - под режим запуска Иксового сеанса (то есть графический). В некоторых дистрибутивах предусматривается еще и многопользовательский режим без поддержки сети. Вот как это выглядит в наиболее распространенном из дистрибутивов Linux - Red Hat (в замужестве - Fedora Core):
0 - останов системы;
1 - однопользовательский режим;
2 - многопользовательский режим без поддержки сети;
3 - полный многопользовательский режим ; 4 - не используется;
5 - полный многопользовательский режим X-сессии;
6 - перезагрузка системы.
В других дистрибутивах Linux количество задействованных runlevels в интервале со 2-го по 5-й и их соответствие режимам может быть иным: для конкретного дистрибутива это можно посмотреть в файле /etc/inittab, в начале которого в виде комментария всегда содержится список, подобный приведенному.
А вообще описание действий по инициализации в файле /etc/inittab начинается с вызова первого исполняемого в ее ходе сценария. Имя его и расположение очень зависят от дистрибутива. В Red Hat, например, это будет /etc/rc.d/rc.sysinit. Функциональность также варьирует от системы к системе, но среди обязательных - такие функции, как проверка файловых систем, их монтирование, активизация swap-раздела (возможно, и многое другое, вплоть загрузки консольных шрифтов, клавиатурных раскладок и даже установки локали).
Следующее действие из предписанных /etc/inittab - определение умолчального режима, то есть все того же runlevels, что воплощается в строке вида
id:3:initdefault:
и автоматически влечет за собой исполнение набора скриптов, за ним закрепленных.
Скрипты эти сгруппированы в определенных (точнее, очень неопределенных - то есть своих в каждом дистрибутиве) подкаталогах каталога /etc. В Red Hat это будут /etc/rc.d/rc0.d, /etc/rc.d/rc1.d, ..., /etc/rc.d/rc6.d, в Debian - /etc/rc0.d, /etc/rc1.d, ..., /etc/rc6.d, в произвольном дистрибутиве Linux - даже и гадать не берусь. Однако общая закономерность в том, что цифра в имени подкаталога прямо соответствует номеру runlevels. И именно скрипты из подкаталога вида /etc*0* будут отрабатываться при останове системы, /etc*1* - при загрузке в однопользовательском режиме, и так далее.
Возникает вопрос - а не повторяются ли сценарии в подкаталогах разных runlevels? Ведь очевидно, что для запуска многопользовательского режима без поддержки сети, полного многопользовательского режима и режима графического нужно выполнить существенно пересекающееся множество действий. И различия лишь в том, что во втором случае добавляется запуск сетевых служб, а в третьем - еще и менеджера графического входа в систему (например, xdm).
Ответ, как ни странно, будет отрицательным. Потому что на самом деле реальных сценариев, соответствующих runlevels, в указанных каталогах нет. Все стартовые скрипты собраны в отдельном подкаталоге с именем вроде /etc/init.d (не поручусь, что оно будет одинаковым во всех дистрибутивах) без всякого разделения, а в подкаталоги вида /etc*#* помещаются лишь символические ссылки на них. Кстати, и упоминавшийся ранее первый инициализационный скрипт /etc/rc.d/rc.sysinit - тоже симлинк на /etc/init.d/rc.sysinit.
Более того, в каждом runlevels-каталоге ссылки эти имеются в двух экземплярах - одна ссылка маркированная буквой S в имени, отвечает за старт сервиса, другая же, имеющая в имени литеру K (от kill), за его остановку.
Это достигается за счет того, что в первом случае скрипт вызывается с опцией start, во втором же - с опцией stop. Исключение - подкаталоги, соответствующие runlevels 0 и 6: из самой их сути очевидно, что стартовых ссылок в них быть не может, а есть только стоповые.
Порядок запуска стартовых сценариев при рассматриваемой их схеме может быть важен: очевидно, что отдельные сетевые службы могут быть запущены только после включения общей поддержки сети. И обеспечивается этот порядок двузначными цифрами в именах ссылок (но не самих скриптов) в каждом runlevels-каталоге: чем меньше цифра, тем раньше запускается сценарий, которому эта ссылка соответствует.
Осталось определить, откуда берутся значения опций и аргументов для команд, образующих сценарии инициализации. А берутся они из собственных конфигурационных файлов - ведь принцип разделения скриптов и конфигов неукоснительно проводится и в схеме SysV. Конфиги эти могут быть собраны в едином подкаталоге типа /etc/conf.d, или раскиданы по всему каталогу /etc - как это сделано в конкретном дистрибутиве, определить не берусь.
Наконец, с запуском сценариев инициализации покончено. Остается последнее из предписанных процессу init действий - получение терминалов. В схеме SysV оно также описывается в файле /etc/inittab и выглядит примерно следующим образом (дано на примере Red Hat/ASPLinux):
# Run gettys in standard runlevels 1:2345:respawn:/sbin/mingetty tty1 2:2345:respawn:/sbin/mingetty tty2 3:2345:respawn:/sbin/mingetty tty3 4:2345:respawn:/sbin/mingetty tty4 5:2345:respawn:/sbin/mingetty tty5 6:2345:respawn:/sbin/mingetty tty6
Впрочем, разговор на эту тему уже был - в Интермедии 13. Добавлю только, что в данном примере 6 виртуальных терминалов активизируются при всех runlevels (кроме однопользовательского режима, разумеется, останова и перезагрузки). А при вызове runlevels 5 (напомню, здесь это - X-сессия) на первой же свободной (то есть не активизированной) консоли запускается менеджер графического входа в систему, за что отвечает такая строка:
x:5:respawn:/etc/X11/prefdm -nodaemon
Я попытался дать максимально обобщенное описание процесса инициализации в стиле System V. Насколько получилось - судить не мне. Однако более ясное представление о ней можно получить, только рассматривая реализации в конкретных дистрибутивах, на что у меня нет ни желания, ни возможности. Так что заинтересованным читателям остается только обратиться к дополнительным источникам информации. Из которых я выделил бы Linux Startup Manual) - с той только оговоркой, что слово Linux в его заглавии следовало бы заменить на Red Hat: ибо в нем описана схема инициализации именно этого дистрибутива. А за более обобщенным описанием схемы SysV и принципов построения собственной схемы загрузки в этом стиле следует обратиться к LFS Book Герарда Бикманса (http://hints.linuxfromscratch.org/) или какому-либо из ее русских переводов: 4-й (http://multilinux.sakh.com/lfs/) или 5-й (http://linux.yaroslavl.ru/docs/book/lfsbook/) версии, номер здесь принципиального значения не имеет.
Схема инициализации в стиле System V может показаться излишне усложненной. Хотя, по словам лично мне знакомых администраторов сетей, позволяет очень гибко управлять различными сервисами. Однако для конечного пользователя такое усложнение неоправданно. И, как выяснилось, это мнение не только мое. Иначе чем объяснить стремление прикрутить сценарии инициализации в BSD-стиле в ряде современных дистрибутивов Linux, таких, как Gentoo, CRUX, Archlinux, не говоря уже о дедушке дистрибутивостроения - Slackware.
Разумеется, и в этом случае никуда не деваются ни runlevels, ни /etc/inittab. BSD-подобный облик схемы их загрузки приобретают за счет а) минимизации задействованных значений runlevels, и б) наличия главного конфигурационного сценария типа /etc/rc, конфигурируемого посредством единого "плоского" /etc/rc.conf. Наиболее последовательно эта схема претворяется в жизнь создателями дистрибутива Archlinux, в котором понятие runlevels практически не используется. Что на конкретно примере будет проиллюстрировано в следующей интермедии.
Старт системы распадается на два
Старт системы распадается на два этапа, лишь опосредованно связанных между собой - собственно загрузку ее и инициализацию.
Под загрузкой операционной системы понимается запуск на исполнение специальной программы, которая называется образом ядра системы (или просто ядром), а также, возможно, сопряженных с ним модулей. Как уже говорилось, образ ядра - почти обычный бинарный исполняемый файл. И специфика его запуска - только в том, что, если любые другие программы запускаются под управлением какой-либо ОС, считываясь с файловой системы, которую эта ОС воспринимает в качестве родной, то ядро обязано запуститься как бы само собой, без всякой операционки (ибо оно-то и есть операционка), и с носителя, о котором система ничего не знает (поскольку сама она еще не загружена). Не случайно в англоязычной литературы для процесса загрузки общепринята метафора называется bootstrapping, что столь же аллегорически можно перевести как "поднятие себя за шнурки своих ботинок". И хотя в реальной жизни такое не каждому удается, в мире POSIX-систем такая процедура осуществляется регулярно - и, как правило, успешно - остается только вспомнить барона Мюнхгаузена, вытащившего себя из болота, дергая за волосы...
Образ ядра содержит все необходимое для чистого bootstrapping'а - загрузочный сектор, первичный загрузчик и собственно исполняемый код ядра. Однако загрузить его с нормальной файловой системы невозможно - ведь о существовании таковых ядро узнает только после того, как будет загружено. Следовательно, образ ядра должен лежать на raw-устройстве (дисковом разделе, дискете, и так далее), файловой системы лишенном. И потому такой способ применяется почти исключительно при использовании для старта Linux или BSD с загрузочной дискеты - например, для аварийно-спасательных работ. В прочих же случаях для загрузки ядра системы применяются специальные программы, именуемые, как и следовало бы ожидать, загрузчиками. Они загружают ядро системы, отвечают за определение оборудования, подгрузку соответствующих ему модулей и запуск первого процесса уже работающей операционки - процесса init.
После этого в действие вступает система инициализации. Ее роль - обеспечить, посредством соответствующих стартовых скриптов (они же - сценарии инициализации), запуск основных системных процессов - сервисов, или демонов. А также - вызвать команды для получения терминала (процессы getty) и авторизации пользователей (login). Окончание старта и знаменуется приглашением к авторизации - все остальное относится уже к сфере влияния пользовательского окружения (userland). Зрительно этапы загрузки и инициализации отличаются тем или иным визуальным представлением выводимых сообщений (если вывод сообщений о ходе стартовых процессов, конечно, не отключен напрочь - в некоторых дистрибутивах Linux встречается и такое). Во FreeBSD и DragonFlyBSD, например, сообщения о ходе загрузки выводятся символами радикально белого цвета, сменяемыми на этапе инициализации обычным приглушенно-белым. В Linux картина внешне прямо противоположна - приглушенно-белая гамма в ходе старта и ярко-белая - при инициализации. А в NetBSD и OpenBSD сообщения о ходе загрузки даются на синем фоне...
Загрузка и инициализация - это первое, что в любой ОС видит пользователь. Правда, пользователю POSIX-совместимой системы такое удовольствие выпадает много реже, чем "подоконнику". Нормальный режим эксплуатации домашней Unix-машины - это ее включение рано утром и выключение - поздно вечером. (Правда, представления о "рано" и "поздно" у всех свои). А служебная Unix-машина по хорошему выключаться вообще не должна - вплоть до полной физической амортизации. Ну а необходимость в рестарте системы по ходу работы в Linux или BSD возникает чрезвычайно редко. Собственно, только после пересборки и реинсталляции нового ядра (или переносе корневого раздела - но это уже вообще исключительный случай) - во всех прочих случаях реконфигурирования системы можно обойтись и без этого.
Так что, казалось бы, что за дело пользователю до того, как протекает старт системы, и сколь долго он длится? Тем не менее, некоторые действия по настройке обоих этапов этого процесса бывают необходимыми.Ибо при старте системы не только выводится некоторая заставка и, возможно, меню с вариантами, но и подгружаются модули ядра, соответствующие наличному оборудованию, монтируются файловые системы, запускаются сценарии инициализации, открываются виртуальные терминалы, и так далее, и тому подобное. Конечно, безусловно обязательным из всех этих действий является только собственно загрузка ядра - прочие могут быть выполнены и впоследствии. Однако не лучше ли сразу по окончании процедуры старта получить полностью готовую к употреблению систему, нежели потом потом доводить ее вручную до этого состояния?
Так что давайте проследим основные стадии ее и посмотрим, где и как (а главное - зачем) в нее можно вмешаться.
Задачи инициализации
Итак, тем или иным способом загрузка ядра и всего сопутствующего ему хозяйства успешно завершена. В дело вступает главный калибр любой POSIX-системы - процесс init. Это первый (в прямом и переносном смысле его PID равен единице) пользовательский (то есть работающий в пользовательском пространстве ядра, юзерланде, процесс, и запускается он исполнением одноименного файла /sbin/init.
В действительности это могут быть (и в разных системах действительно бывают) весьма разные программы. Более того, его можно подменить при интерактивном управлении процессом загрузки другой программой, например, командной оболочкой. Однако это сейчас не очень важно - рассмотрим только штатные задачи программы /sbin/init.
Первой из таких задач, как по времени исполнения, так и по значению, является проверка целостности наличных файловых систем. Для начала каждая из них проверяется на наличие бита "чистого размонтирования" (clean byte), который автоматически устанавливается в ходе правильного завершения предыдущего сеанса работы. Если такой бит обнаруживается на каждой файловой системе - все хорошо, дело движется дальше. Если нет - возможны варианты, о которых я скажу позднее.
Следует отметить, что сам по себе "бит чистого размонтирования" отнюдь не гарантирует сохранности файловой системы и, особенно, ее данных. Он лишь показывает, что файловая система была корректно размонтирована в предыдущем сеансе. В этом случае процесс init делает не лишенное резона допущение, что с метаданными и данными ее все в порядке, и переходит к выполнению следующей задачи.
А следующая задача процесса init - это вызов и отработка сценариев инициализации, или стартовых скриптов, собранных в каталоге /etc и (или) его подкаталогах. Они столь сильно зависят от операционки (а внутри Linux - еще и от конкретного дистрибутива), что дать их обобщенную характеристику практически невозможно. Можно только констатировать, что сценарии инициализации - это обычные сценарии оболочки, рассчитанные на исполнение стандартным POSIX-шеллом (/bin/sh в BSD-системах и, обычно, /bin/bash - в Linux).
Они включают в себя последовательности команд, призванные монтировать файловые системы, активизировать область своппинга, устанавливать системные часы, запускать те или иные службы и демоны, включая сетевые соединения.
Команды, образующие стартовые сценарии, получают свои опции, их значения и аргументы из специальных файлов конфигурации, также имеющих своим местопребыванием /etc и его подкаталоги. Конфигурационные файлы (или по простому конфиги) представляют собой либо простые базы данных опций и аргументов команд, либо списки имен переменных, (соответствующих опциям команд, используемых в скриптах) с присвоенными им значениями. Конфиги от скриптов легко отличимы при просмотре каталога /etc отсутствием у первых бита исполнения.
Сказанное может показаться не очень понятным, поэтому попытаюсь продемонстрировать на примере. Например, обязательная процедура на стадии отработки сценариев инициализации - монтирование необходимых файловых систем в режиме чтения/записи; и перемонтирование - ведь при загрузке ядра несущая его корневая файловая система монтируется в режиме "только для чтения". Это выполняется прямой директивой
$ mount -a
предписывающей смонтировать все файловые системы, и входящей в состав одного из стартовых сценариев (каком именно - зависит от оперционки и дистрибутива). А вот что понимается под словом "все" (имя опции -a - от all) - то есть список аргументов (устройств и точек монтирования), а также опций, с которыми должна быть смонтирована та или иная файловая система, - и составляет содержание специальной базы данных, хранимой в файле /etc/fstab. Мы уже знакомились с ним в , однако позволю себе напомнить обобщенный его формат (т ем паче, что это один из немногих конфигов, формат которого идентичен во всех POSIX-системах).
Это - очень простая база данных, каждая запись которой соответствует подлежащей монтированию файловой системе, а поля, разделителем которых являются символы пробела (пробелов) или табуляции, следующие:
имя файла устройства, несущего файловую систему; точка монтирования - каталог в файловой иерархии; тип файловой системы; опции монтирования (часто имеет значение default).
Содержимое первых двух полей каждой записи передается команде mount из стартового сценария в качестве первого и второго ее аргументов, остальных двух - как опции, обязательные (тип файловой системы) и необязательные (все прочие).
Последовательное разделение стартовых сценариев и их конфигурационных файлов - один из краеугольных принципов общесистемного конфигурирования. В сущности, пользователю при нормальном ходе настройки практически нет необходимости ни знакомиться с содержимым скриптов (хотя это и не вредно), ни, тем паче, менять в них что-либо (последнее допустимо только в том случае, если этот самый пользователь точно знает, что делает, иначе систему легко довести до неработоспособного состояния). А вот вносить изменения в значения параметров конфигурационных файлов - не только можно, но и нужно - разумеется, такое разрешение не избавляет пользователя от понимания смысла своих действий.
Наконец, третья непременная задача процесса init - теч, что в главе 7 мы рассмотрели как получение терминала (запуск процесса getty), установку его свойств и подготовку к авторизации - вытеснение его процессом login. Эта процедура также по разному выполняется в разных системах, хотя тут многообразие не столь и велико, как при отработке стартовых скриптов.
В ходе инициализации могут выполняться и некоторые другие задачи, скажем, конфигурирование приложений, не входящих в базовую систему, но, тем не менее, запускаемых в качестве стартовых сервисов (демонов). Поскольку такие программы устанавливаются отдельно от системы (и не в обязательном порядке), сценарии их запуска и сопряженные с ними конфиги лежат не в каталоге /etc, а в других, подчас весьма неожиданных, местах (/usr/etc, /usr/local/etc и так далее). Типичным примером здесь является httpd - демон, управляющий web-сервером Apache. Однако все это - уже не обязательные составляющие этапа инициализации.
Описанная последовательность инициализации происходит при бессбойном ее протекании - если ни на одной из стадий не происходит ошибок.
Если же таковые случаются - по причине аппаратных ли сбоев или ошибок пользователя при настройке, - все может протекать совершенно иначе.
Первый потенциальный источник ошибок инициализации - отсутствие "бита чистого размонтирования", выявляемое при начальной проверке файловых систем. В этом случае делается вывод о некорректности завершения предыдущего сеанса и возможности существования противоречий в ней. Таковыми могут быть, например, записи в каталогах, которым не соответствуют идентификаторы каких-либо файлов. Или, напротив, идентификаторов файлов, не приписанных ни к каким каталогам.
Для проверки, действительно ли такие противоречия имеют место быть (а отсутствие clean byte отнюдь не влечет их неизбежности) и, при необходимости, исправления выявленного безобразия, запускается утилита проверки файловой системы fsck (это разные программы, в зависимости от типа проверяемых файловых систем). Если такая проверка завершается успешно - то есть противоречий в структуре файловой системы на самом деле нет или могут быть исправлены автоматически - опять же все хорошо, процесс init возвращается к выполнению своих задач, если нет - снова возможны варианты, зависящие от ОС и "тяжести повреждений". Худший случай - когда серьезные противоречия обнаруживаются в корневой файловой системе, это знаменует окончание нормальной инициализации с переходом в однопользовательский режим, когда монтируется только корневая файловая система - и в режиме read only.
Ошибки при отработке скриптов инициализации также влекут разные последствия. Обычный (и наиболее легкий) случай - что сервис, в сценарии запуска которого произошла ошибка (а она может быть вызвана, например, неправильны указанием опций или аргументов в соответствующем конфиге), просто не будет доступным после загрузки системы. Например, так будет с демоном консольной мыши в Linux - gpm, если в его конфиге неправильно был указан протокол или интерфейс мыши.
Более серьезные последствия будут иметь ошибки при монтировании файловых систем, самый частый источник которых - синтаксически неправильное их описание в файле /etc/fstab (то есть элементарные опечатки).
Особенно серьезным будет невозможность монтирования корня файлового древа - этом случае ядро впадет в панику (т.н. Panic mode) и продолжение загрузки окажется невозможным.
Наконец, ошибки в процессе получения терминала (возможные при экспериментах, например, с автоматической регистрацией в системе). Они также достаточно неприятны, и, как правило, приводят к невозможности начала нормальной работы.
Впрочем, оснований для паники нет - даже при впадении в панику ядра, все возможные при инициации системы ошибки исправимы тем или иным способом. Ибо легкий флирт, в том числе и с операционной системой, подобно насморку, переносится на ногах, и постельный режим необходим лишь в тяжелых случаях. В одних ситуациях достаточно завершить процесс загрузки и чуть подправить конфиги, в других - загрузиться в однопользовательском режиме и провести проверку и ремонтирование файловых систем вручную, в третьих придется грузиться с rescue-носителя (например, LiveCD). А вот хирургического вмешательства - сиречь полной переустановки системы, - скорее всего, не потребуется никогда. Впрочем, все это будет предметом отдельного разговора в одной из ближайших интермедий.
Оборотная сторона инициализации системы - это ее останов или рестарт, различий между этими процессами практически нет. И отвечает за него команда shutdown, которая может быть дана от лица суперпользователя или члена группы operator. С опцией -h она вызывает останов машины, с опцией -r - ее перезагрузку. И еще команде этой требуется аргумент - время, когда останов или рестарт должны произойти. Впрочем, есть способ и мгновенного останова или рестарта:
$ shutdown -h now
или
$ shutdown -r now
соответственно.
Во всех, насколько мне известно, POSIX-системах существуют также команды halt и reboot того же назначения. Однако самостоятельной роли они не играют, просто вызывая команду shutdown с опцией останова и перезагрузки, соответственно.
Останов системы происходит в порядке, обратном ее инициализации. Сначала делается попытка корректного завершения всех пользовательских процессов отправкой им сигнала TERM.
По истечении некоторого промежутка времени всем еще "живым" процессам отправляется сигнал KILL - для гарантированного их убиения. Затем стопорятся все стартовые сервисы и демоны. Наконец, содержимое дисковых кэшей записывается на диск (посредством команды sync), и размонтируются файловые системы. После этого обычно появляется сообщение о возможности безопасного отключения питания машины или оно отключается автоматически. При рестарте все происходит точно также, но после останова системы автоматически начинается перезагрузка машины. Весь процесс останова и (или) рестарта определяется соответствующим сценарием (или сценариями), подобными сценариям инициализации.
Важно - и это одно из первых правил техники безопасности, - что Unix-машину крайне не рекомендуется останавливать простым отключением питания. Каковое чревато разными неприятностями. Это могут быть
потери данных в запущенных программа: не случайно первое действие при останове - попытка их сохранения сигналом TERM;
потери как бы сохраненных, но еще не записанных (то есть кэшированных) изменений в открытых файлах;
отсутствие бита чистого размонтирования в нежурналирумых файловых системах, вызывающее их более или менее длительную проверку при рестарте;
более или менее серьезные нарушения целостности файловых систем, вплоть до полного их разрушения (впрочем, последнее нынче случается крайне редко.
В современных версиях POSIX-систем в той или иной мере реализована поддержка управления питанием стандарта ACPI (Advanced Configuration and Power Interface). При ее включении в принципе становится допустим останов системы простым отключением питания машины - на нажатие кнопки Power на корпусе компьютера система реагирует точно так же, как и на команду shutdown -h now. Однако прибегать к этому следует только в случае уверенности в правильности настройки и функционирования модулей acpi - и в Linux, и в BSD-системах эти опции экспериментальны, и их безошибочная работа не гарантируется.
Принципы сборки и установки пакетов
Как уже говорилось в , дистрибутивы Linux организованы по пакетному принципу. Точно также, в виде пакетов, распространяются и любые программы, создаваемые независимыми разработчиками (из которых в основном и собираются дистрибутивы Linux). А в BSD-системы попакетно включаются все приложения, не входящие в состав базового комплекта. И потому одна из важных задач пользователя - это интеграция пакетов в свою систему.
Очень элементарное введение
В большинстве случаев эта задача решается за пользователя разработчиками его операционки: системы управления пакетами (менеджеры пакетов) составляют неотъемлемую часть любого дистрибутива Linux, Free- и прочих BSD. Однако в ряде случаев пользователю приходится устанавливать пакеты и самостоятельно. К тому же понимание сути этого процесса зело способствует уяснению того, что же делают пакетные менеджеры, и помогает принять правильное решение в нештатных ситуациях. Так что вопрос этот заслуживает подробного рассмотрения.
Само по себе выражение "сборка программы" часто повергает начинающего пользователя в некий священный трепет - по себе помню. Однако в этой главе я постараюсь показать, что ничего сверхъестественного в этом процессе нет, и выполнение его по силам любому пользователю, вне зависимости от чисто программистской квалификации - собственно говоря, никаких навыков программирования он не требует, а только - некоторых предварительных знаний.
Однако сначала - маленькое введение для тех, что компьютерное образование начиналось не с книжек Брябрина и Фигурнова, а с руководств типа "Word за 5 минут" или "Quark Press для полных идиотов" (это - не в обиду читателям, но в упрек писателям). Все прочие могут смело пропустить нижеследующие элементарные рассуждения.
Так вот, программы, то есть наборы инструкций, предписывающие машине выполнить то или иное действие (от самых элементарных, типа - скопировать файл, до предельно сложных), сочиняются программистами на языках программирования:-). И представляют они собой самые обычные тексты (называемые исходными текстами, исходниками или, уж совсем жаргонно, сырцами - sources), в которых необходимые инструкции описываются в соответствие с принятыми в данном языке правилами (синтаксисом языка). Собственно говоря, именно это мы и проделывали в параграфе о сценариях из .
Однако не следует через чур уж очеловечивать компьютеры и ожидать от них способности понимать какой-либо "человеческий" язык, даже такой формализованный, как язык программирования.
Нет, они способны воспринять только собственные инструкции (зависящие от центрального процессора), представляемые в бинарном виде - то есть последовательностей нулей и единиц. Собственно, и сами эти цифры - выше их понимания, каковое не выходит за пределы наличия/отсутствия электрического сигнала, но в такие глубины мы лезть уже не будем.
Так что для выполнения программы она в конечном счете должны быть транслирована в соответствующие, зависящие от архитектуры процессора, машинные инструкции. И выполняется этот процесс двояко - посредством интерпретации или компиляции.
Интерпретация - это последовательный перевод языковых конструкций из символов латинского алфавита (и прочих, специальных) в машинные инструкции по мере их ввода. Простейший процесс интерпретации - это ввод директив в командной оболочке (почему она часто называется также командным интерпретатором), а также обработка ее сценариев - наборов элементарных команд. Сколь бы ни был сложен такой сценарий, выполняется он последовательно: сначала интерпретируется команда 1, потом - команда 2, и так далее.
При интерпретации никакого изменения исходного текстового файла не происходит. А сам он отличается от простого списка команд только тем, что имеет бит исполнения. Однако интерпретируемая программа не может исполняться сама по себе, для ее запуска требуется соответствующая среда - программа-интерпретатор (например, та же командная оболочка).
Процесс интерпретации выполняется каждый раз при исполнении программы. И, соответственно, каждый раз время затрачивается на выполнение одних и тех же процедур. Так что возникает резонный вопрос - а нельзя ли оттранслировать исходник программы в машинные инструкции раз и навсегда, и в дальнейшем запускать на исполнение уже набор таких инструкций, не затрачивая время на их преобразование?
Ответ - столь же резонен: ну конечно же, можно. И процедура эта называется компиляцией, а выполняющие ее программы - компиляторами. В ходе этой процедуры из исходного текстового файла по определенным правилам образуется файл бинарный, образованный, если просмотреть его в текстовом редакторе, последовательностью неудобопонятных символов.
И - пригодный для автономного исполнения, для чего необходимости в породившем его компиляторе уже нет.
Предварительно оттранслированные (прекомпилированные) программы, по вполне очевидным причинам, выполняются много быстрее, чем программы интерпретированные, причем разница в скорости нарастает с объемом. И потому все масштабные программы, как правило, пишутся в рассчете на использование в откомпилированном виде. Хотя и роль интерпретируемых программ - сценариев разного рода - в POSIX-системах не стоит недооценивать.
В соответствие с дальнейшим предназначением программ при их написании выбираются инструментальные средства для этого, то есть в первую очередь языки программирования. Которые, таким образом, подразделяются на интерпретируемые и компилируемые.
К первым принадлежат постоянно упоминавшиеся ранее языки командных оболочек. Однако ими список интерпретируемых языков не исчерпывается. В POSIX-системах широко используются такие мощные средства, как Perl, Python, Ruby, Tcl/Tk. Они предоставляют большие возможности, вплоть до создания графических пользовательских инструментов. Однако принципиально написанные на них программы ничем не отличаются от сценариев командной оболочки.
Компилируемых языков - также великое множество, начиная с пресловутого Basic'а и заканчивая специализированными средствами типа Fortran. Однако в POSIX-системах наибольшее значение имеют программы на языке C. Который, собственно, и создавался для разработки первозданного Unix. И на котором написана большая часть ядра всех POSIX-совместимых систем (а ядро ОС - это почти такая же компилируемая программа, как и любая другая), а также большая часть их приложений.
Так что далее речь пойдет о сборке преимущественно C-программ. Однако для нас это существенного значения не имеет - ведь собственно программированием заниматься мы не будем, а принципы сборки практически не зависят от используемого языка. В частности, точно по той же схеме собираются и графические приложения, в которых широко используется язык C++.
Кроме собственно программ, предназначенных для непосредственного исполнения, существуют еще так называемые разделяемые библиотеки, или библиотеки функций (соответствующего языка программирования). Что это такое - проще пояснить на примере.
Все программы, вне зависимости от их назначения, неизбежно должны выполнять некоторые однотипные действия, как то: открыть файл, закрыть его, вывести на экран и так далее. Сущность их не меняется, что бы программа не делала. И потому нет никакого смысла программировать такие манипуляции каждый раз заново.
Вот их, как правило, и не программируют. А объединяют соответствующие директивы в отдельные программные комплексы, именуемые библиотеками. Сами по себе они к автономному исполнению не пригодны. Однако любая программа, при необходимости совершить одно из типовых действий, вызывает из такой библиотеки некий фрагмент кода, содержащий требуемую последовательность директив.
Библиотеки обычно привязаны к определенным языкам программирования, синтаксису которого подчиняются описания директив (т.н. функции - о них вкратце говорилось в заключении главы 12). Поскольку наиболее употребимым в POSIX-системах и их приложениях является язык C, то его функции и требуются чаще всего. Они собираются в главную системную библиотеку, которая именуется обычно libc (Library C), хотя реально это разные комплексы, отличающиеся полнотой функций и их описанием и зависящие от конкретной операционной системы.
В подавляющем большинстве дистрибутивов Linux используется реализация главной системной библиотеки, именуемая glibc (GNU Library C); специализированные дистрибутивы могут использовать и другие библиотеки, например, uclibc, менее функциональную, но более компактную. Главная системная библиотека FreeBSD называется просто - libc, и функционально близка к glibc, хотя и не идентична ей.
Однако libc (glibc) список библиотек не исчерпывается. В POSIX-системах используются библиотеки свойств терминала (например, ncurces) для консольных программ и библиотеки, описывающие процедуры управления окнами - для графических программ системы X (xlib), библиотеки интерфейсных элементов и графических примитивов (Motif, Qt, Gtk), библиотеки описания графических и мультимедийных форматов.Короче говоря, существует тенденция к вынесению в разделяемые библиотеки всех повторяющихся действий и элементов. И в этом - одна из причин компактности большинства классических Unix-программ, в том числе и предназначенных для работы в графическом режиме.
Особенности сборки ядра
Ядро свободной POSIX-системы - это (почти) такая же программа, как и любая другая, распространяемая в исходных текстах. И потому оно также должно быть приведено в пригодное к употреблению состояние посредством сборки. Однако ядро все же - не пользовательское приложение и даже не системная утилита, и потому процесс сборки ядра имеет свою специфику.
Кроме того, ядро - это тот компонент, который и отличает в первую очередь одну POSIX-систему от другой - например, Linux от FreeBSD. Поэтому процесс сборки ядра для каждой операционки имеет свою специфику. Тогда как все сказанное выше касаемо сборки пакетов имеет силу (с очень незначительными оговорками) для любой POSIX-системы - причем даже не обязательно свободной: открытые приложения и утилиты для проприетарной Solaris или AIX в принципе собираются точно так же, как для Linux или какой-либо BSD.
Тем не менее, сборка ядра любой из рассматриваемых ОС включает те же стадии, что и сборка иных приложений - предварительное конфигурирование, собственно сборку и инсталляцию.
Конфигурирование ядра - это включение или выключение поддержки различных устройств (т.н. драйверов, хотя здесь это понятие существенно отличается от принятого в Windows), файловых систем, сетевых протоколов и т.д. Включение поддержки какой-либо опции подразумевает, что в ядро будет встроен код, обеспечивающий работу с неким устройством, файловой системой и проч. Кроме того, многие опции могут быть включены как модули. То есть соответствующий им код компилируется в бинарный вид, но непосредственно в ядро не встраивается, а подгружается в виде отдельной программы по мере необходимости - вручную, соответствующими командами, или автоматически.
Заметим, что, когда речь заходит о драйверах устройств, распространяемых отдельно от ядра операционной системы (например, производителями оборудования - некоторые из них признали ныне факт существования операционок, отличных от Microsoft Windows), имеются ввиду именно загружаемые модули ядра. Подобно другим программам, они могут существовать в виде исходников или в бинарном виде.
В первом случае их теоретически можно собрать для любой версии ядра (или, по крайней мере, для диапазона близких версий), разумеется, только данной ОС (драйверы для Linux, как можно догадаться, не будут работать во FreeBSD, и наоборот). Бинарные же, прекомпилированные, драйверы обычно жестко привязаны к версии ядра, хотя иногда могут как-то работать и при смене оной.
Процесс конфигурирования является наиболее ОС-специфичным во всей процедуре сборки ядра. Само собой разумеется, что ядра разных операционок имеют разные функции и, соответственно, разные опции конфигурирования - еще бы, ведь это все-таки разные программы. Но даже чисто внешне, на пользовательском уровне, процесс конфигурирования ядра Linux и, скажем, FreeBSD (или DragonFlyBSD) существенно отличается.
Во FreeBSD традиционным инструментом конфигурирования ядра выступает обычный текстовый редактор. С его помощью конфиг умолчального ядра (т.н. ядра GENERIC, устанавливаемого при инсталляции системы) приводится в соответствие с потребностями пользователя - одни опции отключаются, другие - включаются.
С отключаемыми опциями все понятно - они просто изымаются из умолчального конфига (путем установки символа комментария на соответствующих строках). А вот откуда взять опции недостающие? Они отыскиваются в некоем образцово-показательном конфигурационном файле. Во FreeBSD 4-й ветки (и это унаследовано в DragonFlyBSD) такой файл носит имя LINT (во FreeBSD для конфигов ядра принято употреблять символы верхнего регистра - этим подчеркивается величие сей программы) и был похож на настоящий - хотя и не работал (в смысле - скомпилировать из него работоспособное ядро было невозможно). В 5-й ветке на смену ему пришел файл NOTES - уже без претензий на всамделишность, это просто список всех теоретически доступных опций конфигурирования, снабженных достаточно подробными комментариями.
Специального включения/выключения модулей в конфигурационном файле ядра FreeBSD не предусмотрено - в качестве таковых по умолчанию собираются все выключенные опции, для которых модульная поддержка в принципе возможна (а она реализована еще не для всех опций).
Правда, это положение можно изменить правкой соответствующих конфигурационных файлов.
В первозданном (или каноническом - том, что скачивается с http://www.kernel.org) ядре Linux никакого умолчального конфигурационного файла не предусмотрено: он генерируется при первом же запуске штатного инструмента для ядерной настройки, базируемого все на той же утилите make - то есть представляющего собой одну из обычных ее целей.
Точнее, не одну - для изначального конфигурирования ядра предусмотрено ажно четыре цели: make config, make menuconfig, make xconfig и make gconfig. Все они делают одно дело - но каждая по своему.
Команда make config вызывает текстовый конфигуратор ядра, работающий в диалоговом режиме. То есть он требует ответа на множество вопросов, для которых предусмотрены варианты ответов - Yes и No, а для многих опций - еще и M (Module), который и обеспечивает подключение модульной поддержки (как и во FreeBSD, таковая возможна не для всех опций).
Использование make config представляется не очень удобным - в случае малейшей ошибки имеется только одна возможность для ее исправления - оборвать программу (например, через Control+C) и начать все заново. И вообще, этот метод конфигурирования считается устаревшим, и в ядрах ветки 2.6.X цель make config штатно не документирована. Хотя и может использоваться при желании - ее преимущество (по моему, несколько сомнительное) в том, что, в отличие от прочих средств конфигурирования, она не требует прав суперпользователя.
Команда make menuconfig, напротив, загружает весьма наглядный меню-ориентированный конфигуратор, богато оформленный псевдографикой. В каждом из подпунктов меню достаточно отметить нужные опции для их включения и, напротив, снять отметки для отключения. Опции, для которых реализована модульная поддержка, могут быть отмечены соответствующим образом.
Использование make menuconfig - пожалуй, наиболее употребимый (и удобный) способ конфигурирования ядра Linux. Нужно только помнить, что запуск соответствующей команды требует полномочий root'а: не то чтобы при этом происходит что-то особо брутальное, просто таковы атрибуты доступа к используемым целью menuconfig скриптам.
Команды make xconfig и make gconfig вызывают конфигураторы ядра, работающие в графическом режиме и, соответственно, могут быть запущены только в терминальном окне сеанса Иксов. В версиях ядра до 2.4.X включительно предусматривалась только первая из указанных целей, которая вызывала достаточно простую меню-ориентированную программу. В ядрах ветки 2.6.X цель make xconfig претерпела существенные изменения: теперь ею вызывается весьма "тяжелая", основанная на библиотеке Qt, программа, требующая также наличия в системе установленной среды KDE и пакета разработки kdevelop, что делает ее практически недоступной для многих пользователей. Ну а make gconfig влечет загрузку аналогичного по функциональности конфигуратора, но основанного на библиотеке Gtk. По причине стойкой нелюбви к последней я этим методом никогда не пользовался и ничего сказать о нем не могу.
Есть в Linux и еще несколько целей, обеспечивающих конфигурирование ядра. Так, командой make defconfig можно сгенерировать некий умолчальный ядерный конфиг - в нем будут включены именно те опции и модули, которые отмечены по умолчанию в меню make menuconfig.
При смене версии ядра можно прибегнуть к цели make oldconfig. Она восстановит конфигурацию ядра прежнего, запросив ответы только на вопросы, касающиеся вновь появившихся опций.
И, наконец, (почти) полный список доступных для конфигурирования ядра и его сборки целей можно получить посредством
$ make help
Собственно сборка ядра и в Linux, и в BSD-системах осуществляется командой make - возможно, с указанием неких конкретных целей. Останавливаться на их описании я сейчас не буду - тому придет свое время. Замечу только, что в Linux дополнительно нужно собрать и модули - командой
make modules
тогда как во FreeBSD это произойдет по умолчанию одновременно с компиляцией ядра.
В результате по завершении сборки у нас в подкаталогах дерева исходников ядра образуется собственно образ ядра - (почти) обычный исполняемый файл, - и набор скомпилированных модулей.
Это такие же объектные файлы, которые возникают на промежуточной стадии компиляции других программ. Так как к самостоятельному использованию они не предназначены, то и в линковке они не нуждаются.
Файл образа ядра во FreeBSD всегда носит имя kernel. В Linux же существует несколько видов образов, за которыми традициями закреплены фиксированные имена - типа vmlinuz, bzImage и еще несколько, о чем мы подробно поговорим тогда, когда до этого дойдет дело.
Как и компоненты любого прикладного пакета, образ ядра и сопровождающие его модули должны быть инсталлированы в должное место - в те подкаталоги, где система ожидает обнаружить их при загрузке. В Linux таких мест традиционно два - каталог /boot или, реже, корень файловой системы, тогда как для модулей предназначается каталог /lib/modules/X.Y.Z, где X.Y.Z - номер версии ядра (например, 2.6.7). Во FreeBSD, начиная с 5-й ветки, под ядро и модули отведен каталог /boot/kernel.В DragonFlyBSD (и остальных BSD-системах) ядро по прежнему размещается в корне файловой системы, модули же - в каталоге /modules.
Так вот, процесс инсталляции и сводится к тому, что файл образа ядра вместе с объектными модулями (а это ныне файлы вида *.ko - для отличия от обычных объектных файлов) копируется в надлежащие каталоги. Для этого предназначены специальные цели команды make. Во FreeBSD это make install или make kernelinstall. В Linux для установки модулей предназначена цель make modules_install, а само ядро может быть инсталлировано различными способами (в том числе - и просто ручным копированием).
В разделе о системных загрузчиках мы увидим, что в принципе ядро может быть размещено чуть ли не в любой части файловой системы. Нужно только позаботиться о том, чтобы программа, выступающая в данной ОС в качестве загрузчика, знала о его расположении. Однако нестандартное размещение ядра может создать некоторые проблемы и в любом случае потребует лишних телодвижений, так что лучше придерживаться традиционной схемы.
Правила сборки
Ну все, с элементарным введением покончено. Переходим собственно к пакетам и их сборке.
Как явствует из названия (и из главы 1), все открытые и свободные программы и разделяемые библиотеки распространяются их разработчиками в исходных текстах. Конечно, никто не запрещает им создавать и прекомпилированные версии своих творений, и многие создатели программ так и поступают, предлагая один или несколько вариантов таковых, рассчитанные обычно на наиболее распространенные дистрибутивы Linux, иногда - FreeBSD, реже - другие BSD-системы (по причинам, которые станут ясными в последующем, прекомпилированные версии зависят от множества факторов, из которых целевая ОС - не последний). Однако это - скорее исключение чем правило.
Наборы исходников объединяются разработчиками в т.н. пакеты, о которых уже упоминалось на протяжении всего этого повествования. Пакет - понятие очень широкое и многогранное. Это может быть и простая монофункциональная утилита (например, строчный текстовый редактор ed или архиватор tar), более или менее обширный набор функционально связанных программ (скажем, coreutils) или огромный программный комплекс (примером чему - XFree86 или Xorg).
Следует оговориться, что термин пакет (английское package) постоянно употребляется в двух смыслах: как набор исходных текстов и как комплект скомпилированных из него программ и всех их служебных файлов. Обычно различие между ними ясно из контекста, в случаях же неоднозначности тот или иной смысл будет оговариваться явно.
Пакеты принято распространять в виде компрессированных архивов - файлов вида *.tar.gz (*.tgz) или *.tar.bz2 (*.tbz2, *.tbz), так называемых тарбаллов. Обычно действует правило: один тарбалл - один пакет. Очень большие пакеты могут быть поделены на несколько тарбаллов (примером чему те же XFree86 или Xorg), но делается это исключительно для удобства скачивания, все равно такой набор тарбаллов исходников сохраняет свою целостность.
Прекомпилированные пакеты подчас также распространяются разработчиками в виде точно таких же тарбаллов.
Но тут уже корреляции пакет - тарбалл может и не быть. Так, XFree86, кроме исходников, доступен также в виде серии скомпилированных пакетов для нескольких дистрибутивов Linux, Free- и OpenBSD. Но это уже - именно самостоятельные пакеты, и не все они обязательны к установке. А сборщики дистрибутивов могут и далее дробить изначально единый пакет, как это обычно делается с теми же Иксами.
И еще. В предыдущих главах я говорил, что BSD-системы, в отличие от Linux, не имеют пакетной организации. Это не совсем точно. Конечно, например, из FreeBSD Distributions нельзя выделить ядро системы или наборы базовых утилит в виде отдельных пакетов. Однако сам по себе он - в сущности единый пакет, только очень большой. А то, что перед пользователем (на CD ли диске, или на ftp-сервере) он предстает перед пользователем в виде кучи мелких (по 1,44 Мбайт) пакетиков - просто наследие тех времен, когда системы еще устанавливались с дискет. В NetBSD и OpenBSD же базовая система собрана в виде единого тарбалла (так и называемого - base.tgz), и уже совсем ничем не отличается от обычных пакетов.
В последнее время и в некоторых дистрибутивах Linux прослеживается тенденция отказа от "квантования" базовой системы. Так, в Gentoo она вся собрана в три тарбалла (stage1, stage2, stage3), и может быть развернута (с различной полнотой, в зависимости от схемы инсталляции) из любого из них. А в Sorcerer и его клонах базовый тарбалл вообще единственный.
Однако я отвлекся, вернемся к нашим исходниками и посмотрим, что с ними нужно делать - ведь ясно, что в том виде, в каком они распространяются, использование их невозможно.
Для того, чтобы программа, распространяемая в исходниках, могла выполнять свои функции, она должна быть собрана. Процесс этот в общем случае разбивается на три стадии:
конфигурирование;
собственно сборку;
инсталляцию.
Конфигурирование - это приведение программы в соответствие с реалиями конкретной системы. Как неоднократно говорилось, подавляющее большинство свободного софта пишется в рассчете на некую абстрактную POSIX-совместимую ОС.
Конкретные же их представители отличаются друг от друга многими деталями, в частности - функциональностью библиотек, их названиями и расположением. Что и проверяется в процессе конфигурирования. То есть основное назначение его - проверка так называемых зависимостей пакетов.
Понятие зависимостей - одно из основных при сборке программ. Суть его в том, что пакет pkgname1 для установки и (или) функционирования требует наличия в системе пакета pkgname2, тот, в свою очередь, может потребовать пакета pkgname3, и так далее. В качестве зависимостей выступают часто (хотя и не всегда) те самые системные библиотеки, о которых говорилось ранее.
Зависимости пакетов бывают разными. С одной стороны, различают зависимости при сборке (обычно называемые просто зависимостями - depends) и зависимости при запуске (по английски именуемые run depends).
Как следует из названий, при зависимости пакеты, от которых зависит данный, необходимы только на стадии сборки пакета, тогда как зависимости второго рода действуют постоянно. В большинстве случаев depends и run depends эквивалентны, однако это правило имеет многочисленные исключения. Так, при статической сборке (что это такое - будет говориться чуть позднее) библиотеки, которые использует данный пакет, требуются только в момент компиляции - в дальнейшем необходимости в них не возникает.
С другой стороны, следует различать зависимости жесткие и "мягкие". Удовлетворение первых абсолютно необходимо для сборки данного пакета. Так, практически любая программа использует (статически или динамически) главную системную библиотеку glibc (или libc), любое приложение для системы X - главную Иксовую библиотеку xlib, все приложения для интегрированной среды KDE - библиотеки qt и kdelibc.
"Мягкие" зависимости данного пакета не критичны для его функционирования - удовлетворение их лишь добавляет ему дополнительные функции (которые могут оказаться и лишними).
Понятие зависимостей пронизывает насквозь POSIX-совместимые системы, и особенно важно для свободных их представителей.
В то же время пользователи Windows с ним сталкиваются очень редко, и потому постижение его вызывает определенные трудности у недавнего подоконника. Это связано с двумя факторами.
Во-первых, традиционная модель разработки Unix-программ (то, что задумчиво именуют Unix Way) характеризуется ярко выраженным стремлением не множить сущности без крайней необходимости. Или, говоря попросту, не изобретать велосипеды. То есть: если требуемая разработчику данной программы функция уже реализована и включена в какую-либо распространенную библиотеку, то наш разработчик скорее всего этой библиотекой и воспользуется, а не будет переписывать ее с нуля. Благо, поскольку все распространенные и общеупотребимые библиотеки открыты, он имеет полную возможность это сделать (вспомним о смертном грехе лености).
Возможно, разработчик Windows-программы с удовольствием последовал бы примеру братьев-POSIX'ивистов. Однако исходники Windows-библиотек в большинстве своем закрыты и защищаются всякого рода проприетарными лицензиями, препятствующими их свободному использованию. И потому Windows-разработчику волей-неволей приходится реализовывать требуемые ему функции самостоятельно. В результате чего программа, хотя и приобретает определенную самодостаточность, но зато разбухает в размерах: вспомним, сколько файлов вида *.dll устанавливает элементарный графический вьювер, идущий в комплекте со сканером или цифровой камерой.
За конфигурирование обычно отвечает сценарий, расположенный в корне дерева исходников данной программы и, по соглашению, носящий имя configure. Что именно делает конкретный конфигурационный скрипт - сугубо на совести разработчика программы. Как минимум, он обязан проверять жесткие зависимости устанавливаемого пакета и, при их нарушении, выдавать соответствующие сообщения. Кроме того, он может обеспечивать также подключение дополнительных функций - при наличии определенных условий, то есть удовлетворении зависимостей "мягких".
Так, для консольных программ в Linux существует возможность использования мыши в качестве указательно-позиционирующего устройства (а не только для выделения/вставки экранных фрагментов, как это имеет место в BSD-системах).
Обеспечивается эта функция специальным сервисом - gpm. И во многих программах (таких, как файловый менеджер Midnight Commander или текстовый браузер links) конфигурационный скрипт проверяет, установлен ли пакет gpm, и при наличии его - автоматически задействует использование мыши.
Если процесс конфигурирования завершается успешно, в корне дерева каталогов создается специальный файл - Makefile, в котором и фиксируются все предусмотренные разработчиком настройки, выступающие в качестве директив на следующем этапе.
Если конфигурирование прошло с ошибками, выдается соответствующее сообщение, форма которого также целиком определяется разработчиком. Ошибки эти могут быть связаны с нарушением жестких зависимостей пакета, и в этом случае никакие дальнейшие действия, до их разрешения, невозможны. Если же конфигурационный сценарий выявил нарушение "мягких" зависимостей, то пользователь обычно может отказаться от них, просто потеряв некоторую дополнительную функциональность. Которая, к тому же, вполне может быть ему не нужной. Так, например, я всегда отказываюсь от поддержки мыши (через gpm) в консольных Linux-программах. Правда, это может потребовать указания некоторых опций конфигурирования (о чем я скажу чуть ниже). Правда, обычно это требует указания некоторых дополнительных опций исполнения скрипта configure, о которых будет сказано ниже.
Образцово-показательный отчет о выполнении сценария configure выдают, по моему мнению, пакеты, штатно входящие в состав интегрированной среды KDE. Во-первых, их конфигурирование не обрывается сразу же после нахождения первой ошибки (первого нарушения зависимостей - например, отсутствия каких-либо мультимедийных или графических библиотек), как это бывает в большинстве других программ, а в любом случае доводится до конца. После чего сообщается, что такие-то компоненты необходимы для сборки данного пакета (то есть связаны с ним жесткими зависимостями), другие же - требуются для получения определенных функций (например, наличие пакета cups - для обеспечения печати на принтере, пакета sane - для сканирования, и так далее).
И пользователю вольно решить - устанавливать ли ему "мягко-зависимые" пакеты, или он, за отсутствием сканера или принтера, вполне может обойтись без них.
Наконец, конфигурирование завершилось успешно. Наступает время следующего этапа - собственно сборки, то есть претворения исходных текстов программы в исполняемый машинный код. Этап этот распадается на несколько стадий.
Первая стадия - собственно компиляция исходного текста в бинарный код, завершающаяся формированием т.н. объектного модуля. Это - как правило, еще не готовая к запуску программа. Почему? Да потому, что в его скомпилированном коде может не быть многих стандартных функций - тех самых, которые разработчик предполагал заимствовать из разделяемых библиотек.
И потому вторая стадия - это связывание (linking, в просторечии именуемое линковкой) сгенерированного кода с необходимыми библиотечными фрагментами. Линковка может быть двух видов - статическая и динамическая. В первом случае требуемый код из библиотеки встраивается внутрь собираемой программы, после чего получается готовый к исполнению бинарный файл, более в библиотеке не нуждающийся. Это - именно тот случай, когда понятия depends и rdepends приобретают разное значение: первое оказывается шире.
Второй случай - динамической линковки, - предполагает, что библиотечный код не встраивается в программу: вместо него устанавливается только ссылка на файл библиотеки и требуемую функцию (имя которой извлекается из так называемого заголовочного файла - header-файла). И в дальнейшем, при запуске исполняемого модуля программы, соответствующие библиотечные фрагменты извлекаются с диска и присоединяются к коду программы уже только в оперативной памяти. При этом сущности depends и rdepends оказываются идентичными: библиотека, с которой программа связывается при сборке, столь же необходима и для ее запуска.
Динамическая линковка приводит как к сокращению размера исполняемого файла, так и уменьшению объема оперативной памяти, задействуемого при запуске программ (особенно если они используют одни и те же библиотечные функции - а в большинстве случаев так оно и есть).
И потому именно она преимущественно используется при сборке программ для свободных POSIX-систем (повторю еще раз, что разделяемые библиотеки в них открыты и могу применяться без ограничений).
Однако бывают ситуации, когда приходится прибегать к линковке статической. Так, во FreeBSD статически линкуются с главной системной библиотекой жизненно важные для запуска и восстановления системы утилиты (в предыдущих версиях этой ОС они располагались в каталогах /bin и /sbin, во FreeBSD 5-й ветки для них отведен специальный каталог /restore). В результате они оказываются доступными (и пригодными к исполнению) даже в случае аварийной загрузки, когда все файловые системы, кроме корневой, не монтируются (а разделяемые библиотеки вполне могут располагаться на самостоятельных физических носителях со своими файловыми системами).
Третий этап процесса сборки - инсталляция. Это - инкорпорация всех компонентов программы в структуру файловой системы данной машины. Или, по простому, по бразильскому - их копирование в соответствующие каталоги файлового древа (по завершении сборки они могут находиться в самых разных местах, обычно - в подкаталогах дерева исходников). Как правило, для разных компонентов пакета существуют традиционно предопределенные имена каталогов, в которых они должны размещаться: bin или sbin - для исполняемых модулей, lib - для библиотек, etc - для конфигурационных файлов, share - для всякого рода документации и примеров, и так далее.
Предопределенные имена каталогов не обязательно будут ветвями корня файловой системы (типа bin, /sbin и так далее). Точнее, в общем случае, не будут: более вероятно, что соответствующие компоненты собираемого пакета помещаются в каталоги /usr/bin, /usr/local/bin и так далее. Впрочем, к обсуждению этого вопроса мы скоро вернемся.
Так вот, процесс инсталляции и сводится к тому, что исполняемый файл (файлы) собранного пакета копируется в файл ~/bin (или, для программ системного назначения, в ~/sbin), ее конфиг - в ~/etc, страницы документации - в ~/man, и так далее (~/ в данном случае символизирует не домашний каталог пользователя, а некий условный префикс - см.далее).
По завершении всего сборочного цикла из пакета исходников должна получиться полнофункциональная, готовая к употреблению, программа, требующая лишь некоторой пользовательской настройки.
Средства управления пакетами
Вопросу ручной сборки программ выше было уделено чень много места. Однако на самом деле пользователю не так уж и часто приходится заниматься этим делом. Потому что любая уважающая себя система, как было сказано в главе 2, обладает тем или иным способом автоматизации установки и удаления программ, избавляющей не только от отслеживания зависимостей, но обычно даже и от компиляции, то есть системой управления пакетами.
Средства управления пакетами чрезвычайно разнообразны. В первом приближении их можно разделить на два класса - системы портов и собственно системы пакетного менеджмента.
Системы портов пришли из мира BSD, где впервые появились во FreeBSD. Собственно, порты - это родовое имя системы пакетного менеджмента этой операционки. Все остальные портообразные системы возникли и развивались под сильным ее влиянием. Наибольшее распространение и известность получили pkgsrc и портежи (portages).
Первая система возникла первоначально в NetBSD, постепенно превратившись в кроссс-платформенное средство управления пакетами: существуют ее версии для всех BSD-систем (включая DragonFly), ряда дистрибутивов Linux и даже для таких проприетарных Unix'ов, как Solaris и IRIX.
Система портежей была разработана для дистрибутива Gentoo Linux. Однако и она быстро обрела черты кросс-платформенности: более или менее активно разрабатываются ее варианты для FreeBSD и NetBSD, поговаривают о переносе ее на Hurd и другие дистрибутивы Linux.
Тем не менее, практически каждый дистрибутив Linux, относимый к классу Source Based, обладает собственной, более или менее оригинальной, портообразной системой (почему я и полагаю, что их следует называть скорее портируемыми дистрибутивами). Тут следует упомянуть и ABS - Archlinux Building System из одноименного дистрибутива, и порты CRUX, и Sorcery из Sorcerer Linux и его потомков (Source Mage и Linar). И примеры портообразных систем множатся с каждым днем: только за истекший год появились дистрибутивы Rubyx и MyGeOS - каждый со своей портообразной системой.
Не смотря на такое разнообразие конкретных реализаций, общие черты любой портообразной системы вычленить очень легко: это набор правил для отыскания в Сети исходников устанавливаемых программ, их получения на локальную машину, развертывания тарбаллов, конфигурирования, сборки (компиляции и линковки), инсталляции в файловую систему и, возможно, постинсталляционного конфигурирования. То есть по этим правилам выполняется весь тот цикл действий, которые мы проделываем при ручной сборке любой программы (вспомним три волшебных заклинания ./configure, make, make install из предшествующих параграфов), но - в автоматическом режиме. Однако в добавок к этому система портов включает такойму важный для управления пакетами компонент, как средства выявления и отслеживания зависимостей, как "жестких", обязательных, так и "мягких", опциональных, коррекции последних в ту или иную сторону, ну и, конечно же, автоматического удовлетворения тех и других.
Собственно системы пакетного менеджмента - это инструменты для установки и удаления ранее скопмилированных бинарных пакетов. Что такое бинарный пакет - легко представить себе, если вернуться к процессу сборки программ, описанному выше. Как мы помним, собственно стадия сборки (исполнение процедуры make) завершается тем, что полностью собранные, готовые к употреблению, компоненты пакета располагаются в промежуточном каталоге.
По умолчанию каталог для промежуточных продуктов сборки - то же самый, что и для исходных текстов. Однако, если на стадии конфигурирования задать какой-либо иной адрес сборки, например
$ ./configure --prefix=/path_to/pkgname
то бинарные компоненты пакета будут собраны в отдельном каталоге с подкаталогами pkgname/bin, pkgname/lib, pkgname/share и так далее. И если этот каталог заархивировать утилитой tar и сжать посредством gzip или bzip2 - то получится автономный бинарный пакет в простейшей своей форме - в виде простого тарбалла. Для использования остается только развернуть его и инкорпорировать в древо файловой системы.
Эту задачу и выполняют система пакетного менеджмента, обеспечивая заодно и контроль зависимостей при установке.
По формату пакеты в первом приближении можно разделить на две группы - те, что содержат внутри себя метаинформацию (в частности, информацию о зависимостях пакетов), и таковой лишенные.
К первой группе относятся широко распространенные форматы пакетов rpm (Red Hat Packages Manager, характерный для одноименного дистрибутива и его многочисленных потомков) и deb (свойственный дистрибутиву Debian и на нем основанным). И тот, и другой, помимо собственно архива бинарных файлов и путей к ним, содержат данные о зависимостях, хотя и представленные в разной форме.
Пакеты без метаданных - это обычные тарбаллы, то есть компрессированные tar-архивы типа *tar.gz/*tar.bz2 (часто фигурирующие в форме tgz и tbz). Важно, что сами по себе tgz,tbz и им подобные - это форматы вовсе не пакета, а именно архива (то есть определяются используемой утилитой компрессии - gzip или bzip2, соответственно). А важно это потому, что те же самые tgz/tbz архивы могут прекрасно содержать в себе и метаинформацию, оказываясь более сходными с пакетами rpm или deb (и ниже мы столнемся с примерами этого). Характерным примером чего являются packages BSD-систем, включающих в себя полное описание зависимостей.
Еще более существенно то, что отсутствие в составе пакета информации о его зависимостях отнюдь не препятствует контролю над ними: он может осуществляться за счет внешних баз данных репозиториев пакетов и локальных баз данных пакетов установленных. А функции удовлетворения зависимостей в этом случае целиком ложатся на программы, осуществляющие пакетный менеджмент. И надо отметить, что управление "чистыми" тарбаллами подчас оказывается более гибким, чем пакетами с информацией об их заивисимостях.
Средства пакетного менеджмента жестко привязаны к формату пакетов - для установки rpm-пакетов служит одноименная утилита (rpm), пакеты deb устанавливаются посредством утилиты dpkg, для пакетных тарбаллов предусмотрены собственные средства, обычно - дистрибутив-специфичные, не смотря на похожие, и даже подчас одинаковые, имена.
Конечно, существуют средства взаимной трансформации пакетов разных форматов (типа rpm2cpio, rpm2tgz или почти универсальной утилиты alien), однако возможности их применения ограничены - очевидно, что из rpm-пакета (и тем более "чистого" трабалла) получить полноценный deb-пакет невозможно.
Однако существуют еще и средства пакетного мета-менеджмента, если так можно выразиться (собственно, только они-то и заслуживают названия систем управления пакетами). Наиболее известное и распространенное из них - apt. Появившийся сначала в Debian'е и рассчитанный, соответственно, на deb-пакеты, он очень быстро стал универсальным кросс-пакетным механизмом установки, удаления и обновления программ, успешно работая с пакетами rpm (дистрибутивы Connectiva, Altlinux), тарбаллами Slackaware (механизм slapt-get). И в принципе не видно препятствий к прикручиванияю его к тарбаллам любого формата - от "чистых" до сколь угодно насыщенных метаинформацией.
Под явным влиянием apt возникли и иные системы пакетного менеджмента - yum, urpmi и так далее. Однако они ориентированы только на rpm-пакеты (вероятно, их можно использовать и для иных форматов, но кому это нужно при наличии apt?) и потому не получили столь широкого распространения, оставаясь принадлежностью "родительских" дистрибутивов и более-менее точных их клонов (yum, насколько мне известно, используется только в Red Hat/Fedora и ASPlinux).
Следует помнить, что резкую границу между системами портов и средствами пакетного менеджмента провести нелегко. Все портообразные системы позволяют выполнить сборку автономных бинарных пакетов и, соответственно, включают средства их установки, регистрации и удаления. А в системах пакетного менеджмента можно обнаружить инструментарий для автоматической компиляции программ из исходников, хотя они и играют сугубо вспомогательную роль.
Три волшебных слова
Теперь, разобравшись с принципами, посмотрим, как сборка пакетов осуществляется на практике.
Понятное дело, что перво-наперво тарбалл исходников следует декомпрессировать и развернуть в каком-либо подходящем каталоге. Для самостоятельно собираемых исходников я использую обычно каталоги вроде $HOME/src или, в некоторых случаях, /usr/local/src (разумеется, для этого нужно обладать правами на запись в тот или иной). Как станет ясным из дальнейшего, удаление развернутого дерева исходников установленных программ очень нежелательно, поэтому следует озаботиться наличием достаточного количества свободного места в той файловой системе, в которой выполняется распаковка.
Сама по себе распаковка делается обычным образом, например, командой tar:
$ tar xzpvf /path_to_src/tarball.tar.gz
или
$ tar xjpvf /path_to_src/tarball.tar.bz2
в зависимости от использовавшейся для компрессии программы (gzip или bzip2, соответственно). Кратко остановлюсь на смысле опций (команда tar будет предметом отдельного рассмотрения впоследствие).
Опция x (от eXtract) предписывает развертывание архива. Однако поскольку он был ранее сжат утилитой компрессии, его предварительно нужно декомпрессировать - этому служит опция z при gzip или j при bzip2. Опция f имеет своим значением имя подвергающегося развертыванию/декомпрессии файла - в примере tarball.tar.*. Опция v не обязательна - она заставляет выводить на экран сообщения о ходе распаковки.
А вот опция p может быть важной: она предписывает сохранять атрибуты доступа и принадлежности теми же, что были у оригинальных файлов до их упаковки в тарбалл. Без нее хозяином всех новораспакованных файлов оказался бы пользователь, выполняющий процедуру распаковки. Обычно это не имеет значения, но в некоторых случаях - нежелательно, или просто не должно быть (например, при распаковке прекомпилированных тарбаллов stage1-3 в Gentoo). Так что лучше взять себе за правило не забывать про эту опцию никогда.
Если пакет состоит из нескольких тарбаллов, все они должны быть распакованы.
Повторять несколько раз какую- либо из приведенных выше команд было бы скучно - однако эту процедуру можно выполнить в один присест. ИМХО, самый простой способ для этого - прибегнуть к универсальной утилите find, что в данном случае будет выглядеть примерно так:
$ find /path_to_src -name *.tar.gz -exec tar xzpvf {} \;
В результате любой из описанных процедур в текущем каталоге должен образоваться подкаталог вида package_name-version, то есть соответствующий имени пакета с указанием номера его версии и, иногда, реализации (реже - просто имени пакета). Но это - только в том случае, если исходный тарбалл был сформирован корректно, с включением корня дерева исходников. Редко, но бывает так, что разработчик забывает о такой мелочи. И потому, дабы не получить в текущем каталоге неудобопонятной мешанины файлов, перед собственно распаковкой лучше выполнить проверку на вшивость, командой
$ tar -tzvf tarball.tar.gz
где опция t (от lisT) и предписывает вывести список файлов тарбалла вместо его развертывания.
Дальнейшие действия по сборке пакета в большинстве случаев осуществляются путем последовательной отдачи трех команд - ./configure, make, make install.
Первую из этих трех команд следует давать, перейдя предварительно в корень дерева исходников нужного пакета:
$ cd /path2srcpkg $ ./configure
Это - запуск того самого конфигурационного скрипта, о котором давеча говорилось. Обращаю внимание на ./ - эти символы являются указателями на текущий каталог (/path_to_srcpkg), в котором расположен файл сценария (а мы помним, что текущий каталог, как правило, не включается в число значений переменной $PATH).
Сценарий configure имеет некоторое количество опций. Число их и назначение определяются разработчиком, однако некоторые - встречаются практически всегда. И важнейшая из них - это опция --help, выводящая полный список всех других опций. Строго говоря, именно с команды
$ ./configure --help
и следует начинать самостоятельную сборку любого пакета, особенно - не знакомого. Прочитав предварительно файлы README и INSTALL - минимум один их таковых, скорее всего, имеется в корне дерева исходников, - которые содержат более или менее подробную информацию о программу, в том числе - и о порядке ее сборки.
Однако полного перечня опций конфигурирования там не будет - так что ознакомимся с наиболее обычными из них посредством вышеуказанной команды.
В аккуратно написанных программах вывод команды ./configure --help обычно распадается на несколько секций. Первой, как правили, идет секция
Installation directories:
в которой указываются каталоги, куда в дальнейшем будут устанавливаться отдельные компоненты собранного пакета. Важнейшей опцией здесь является --prefix=PREFIX. Значением PREFIX будет выступать ветвь корневого каталога, в подкаталоги которого запишутся исполняемые файлы, конфиги, библиотеки и т.д. По умолчанию эта опция в большинстве случаев имеет значение /usr/local. То есть в случае, если значение опции --prefix при запуске скрипта ./configure не задано, то исполнимые файлы пакета будут инсталлированы в /usr/local/bin, конфиги - в >/usr/local/etc, и так далее.
Так что если желательно размещение компонентов собираемого пакета в каталоге, отличном от умолчального (например, в /usr), значение перфикса следует задать в явном виде, скажем, так:
./configure --prefix=/usr
В последнее время некоторые пакеты предполагают установку по умолчанию в подкаталоги каталога /opt - /opt/pkgname/bin, /opt/pkgname/lib, и так далее. А для KDE-приложений последних версий умолчальное значение перфикса - /opt/kde (/opt/kde/bin, /opt/kde/lib и так далее). Для таких программных комплексов лучше его не менять - во избежание осложнений при поиске библиотек.
А вообще, значение опции --prefix может быть любым. В частности, если предполагается сборка пакета для дальнейшего автономного его распространения в бинарном виде, целесообразно сосредоточить все его компоненты в отдельном подкаталоге, например, вида $HOME/my_pkg/pkg_name. Аналогично следует поступать и при тестировании пакета, предшествующем его инсталляции.
Далее в той же секции обычно имеет место быть опция --bindir=DIR. И здесь значением DIR выступает обычно PREFIX/bin. Однако в некоторых случаях исполнимые файлы пакета целесообразно поместить в иные ветви файловой системы.
Например, если вручную собирается командная оболочка, которая будет выступать в дальнейшем как login shell, или любимый (=общесистемный) текстовый редактор, очень желательно, чтобы их исполняемые бинарники находились непосредственно в каталоге корневой файловой системы (иначе они могут быть недоступны в аварийных случаях или при старте в однопользовательском режиме). И тут, вне зависимости от того, задано ли значение опции --prefix или нет, конфигурационный скрипт следует запускать в такой форме:
$ ./configure --bindir=/bin
или, для программ административного назначения, -
$ ./configure --bindir=/sbin
Нередко в секции Installation directories можно обнаружить и другие опции, предписывающие размещение библиотек, страниц документации и тому подобных компонентов пакета.
Следующая почти непременная секция вывода помощи конфигурационного скрипта -
Optional Features:
Именно в ней, как легко понять из названия, перечисляются опции, позволяющие подключить/отключить дополнительные возможности собираемого пакета. Они имеют вид
--enable-FEATURE
и
--disable-FEATURE
Первая, естественно, подключает возможность с именем FEATURE, а вторая - отключает оную. Причем одна из этих опций может быть принята по умолчанию. Например, большинство свободных программ ныне собирается с поддержкой национальных языков (National Labguage Support - NLS), обеспечивающих вывод сообщений, пунктов меню, помощи т т.д. на языках, отличных от американского (при наличии соответствующих ресурсов, разумеется - если систему помощи некоего пакета никто не удосужился перевести на русский язык, то включай NLS, не включай - все едино, русского хелпа от нее не получишь). Однако в ряде случаев это может показаться нежелательным - и тогда при конфигурировании программы нужно задать соответствующею опцию:
$ ./configure --disable-nls
Обычно допустима и иная форма этой опции:
$ ./configure --enable-nls=no
Функции, подключаемые (или отключаемые) посредством опции --enable(disable)-FEATURE, берутся из библиотечных пакетов.
В частности, за поддержку NLS отвечает библиотека gettext (подчеркну, что сама по себе эта библиотека не дает возможности волшебным образом получать сообщения на русском или там эскимосском языке, а только обеспечивает принципиальную возможность вывода таковых).
Сходный смысл имеют опции, входящие в секцию
Optional Packages:
общий вид которых таков:
with-PACKAGE
или
without-PACKAGE
Отличие опций этой секции от тех, что перечислены в секции Optional Features - в том, что в качестве значений PACKAGE выступаю не некие абстрактные функции, а имена конкретных пакетов, возможности которых добавляются к собираемому (или отнимаются от оного).
Опции вида with-PACKAGE могут также иметь значения - yes, no или auto. Последняя обычно принята по умолчанию. То есть, если в ходе выполнения конфигурационного скрипта пакет с именем PACKAGE будет обнаружен в данной системе, его возможности будут подключены автоматически, если нет - проигнорированы.
Так, в приводимом ранее примере с поддержкой указующе-позиционирующих свойств мыши при сборке консольных программ типа links или mc они будут автоматически задействованы по умолчанию, если в Linux-системе (к BSD это не относится) обнаружится установленный пакет gpm. Если же он имеется, но поддержка мыши для данного пакета представляется нежелательной, это следует указать в явном виде:
$ ./configure --without-gpm
И последняя секция, практически всегда присутствующая в выводе помощи конфигурационного скрипта -
Some influential environment variables:
Как следует из названия, здесь перечисляются различные переменные окружения, могущие оказывать влияние на процесс компиляции. Наиболее часто в качестве таких переменных предусматриваются флаги компилятора gcc типа CFLAGS и CXXFLAGS (для программ на языке Си и Си++, соответственно). Обычное употребление таких флагов - задание всякого рода оптимизаций - общего ее уровня, архитектуры целевого процессора, конкретных наборов его команд, и так далее. Например, оказание опции
$ ./configure CFLAGS="-O3 -march=pentium4"
обеспечит максимальный уровень оптимизации (значение -O3) для процессора Pentium4 (значение -march=pentium4) - опять же заостри внимание на кавычках, в которые эти значения заключены (дабы восприниматься как единый аргумент). Впрочем, тема оптимизации будет предметом особого рассмотрения.
Ранее я говорил, что при сборке пакеты могут связываться с библиотечными функциями как статически, так и динамически. Так вот, характер связи также определяется на стадии начального конфигурирования. По умолчанию во всех известных мне случаях используется динамическая линковка. Что связать исполняемый модуль с библиотекой статически, требуется специальный флаг - LDFLAGS. Значениями его в данном случае будут
$ ./configure LDFLAGS="-static"
или, в случае линковки с несколькими библиотеками -
$ ./configure LDFLAGS="-all-static"
Вообще говоря, опции конфигурирования пакета могут быть очень разнообразны - я перечислил лишь наиболее часто встречающиеся. Общие рецепты эффектвиного их использования - а) внимательное чтение вывода помощи конфигурационного скрипта, б) знание особенностей своей собственной системы (в первую очередь - мест размещения разделяемых библиотек) и в) конечно же, здравый смысл.
Надо отметить, что некоторые пакеты не имеют конфигурационного сценария в дереве исходников. Это не обязательно следствие лени разработчика: может быть, что программа настолько проста, что в предварительном конфигурировании не нуждается (например, не использует функций никакой разделяемой библиотеки).
Другая возможная причина отсутствия скрипта configure - то, что предварительное конфигурирование уже выполнено разработчиком. В этом случае в дереве исходников можно обнаружить уже готовый Makefile, рассчитанный на некоторые типовые ситуации, или несколько его вариантов - для разных архитектур, операционных систем и так далее. Если же ни один из предложенных автором вариантов не отвечает в полной мере реалиям пользователя - у него остается последний выход: ручная правка Make-файла (обычно такие случаи документируются в файлах типа README или INSTALL./p>
Предварительное конфигурирование пакета - очень важный момент в его сборке: можно сказать, что успех ее на 90% определяется именно в результате исполнения скрипта configure. Однако рано или поздно оно завершается удачно (о случаях фатального невезения я скажу несколько позже). И наступает время собственно сборки, для чего предназначено второе из наших магических заклинаний - команда make.
Сама по себе команда make не выполняет ни компиляции (это - дело компилятора gcc, ни линковки (с этой ролью справляется редактор связей ld), ни каких-либо иных действий по превращению исходного текста в машинный код. Задача ее - интеграция всех требуемых средств (а в процессе сборки могут задействоваться и т.н. препроцессоры, и языковые анализаторы, возможно, и иные инструменты), чтобы автоматически получить (почти) готовые к употреблению бинарные компоненты пакета. Собственно говоря, от пользователя требуется только дать директивное указание - набрать в командной строке make и нажать Enter - все остальное произойдет как бы само собой. С другой стороны, у него нет и возможности вмешаться в процесс сборки (разве что прервать его комбинацией клавиш Control+C:-)).
Конечно, и команда make способна воспринимать многие параметры командной строки. Правда, в большинстве случаев они дублируют опции, заданные при конфигурировании. Так, при отдаче директивы make можно задать флаги оптимизации
$ make CFLAGS="-O3 -march=pentium4"
предписать статическую линковку с разделяемыми библиотеками
$ make LDFLAGS="-static"
или предписать последующую установку компонентов пакета в каталог, отличный от умолчального. Однако - и все: далее остается только дожидаться успешного (надеюсь) окончания сборки.
Однако у команды make есть еще один важный вид аргументов командной строки - так называемые цели (target). Собственно для сборки по умолчанию они обычно не требуются. Хотя некоторые пакеты требуют их задания в явном виде. Так, оконная система X штатным образом собирается с указанием цели world:
$ make world
В других случаях для достижения того же результата может применяться и иная цель, например
$ make all
или указание на конкретную архитектуру, операционную систему, и т.д. Все такого рода исключения, как правило, описаны в сопроводительной документации. На худой конец (если таковой не имеется), допустимые для данного пакета цели команды make можно подсмотреть в Makefile - там они будут определены обязательно.
Однако есть у команды make и практически непременная цель - install, предписывающая выполнить установку компонентов пакета в надлежащие места файловой системы (умолчальные или определенные на этапе конфигурирования или сборки). И это - третье, и последнее, из наших шаманских заклинаний:
$ make install
После чего мы наконец получаем готовую к употреблению программу.
В некоторых программах цель install разработчиком не предусматривается. И тут приходится вручную скопировать скомпилированные модули в подходящие каталоги. Правда, такое бывает очень редко и, как правило, для очень простых по устройству программ.
В принципе команду make install можно было бы дать и сразу по исполнении сценария ./configure. В этом случае сначала будет исполнена умолчальная цель make - компиляция и линковка пакета, а затем его инсталляция. Однако делать это не всегда целесообразно. Во-первых, такое совмещение целей затрудняет отслеживание ошибок. Во-вторых, их совместному исполнению может помешать отсутствие должных прав доступа.
Дело в том, что стадии конфигурирования и сборки обычно могут быть выполнены от имени обычного пользователя - это определяется правами доступа к каталогу, в который было распаковано дерево исходных текстов пакета. А вот установка собранных компонентов почти наверняка потребует административных привилегий. Ведь исполняемые файлы пакета после директивы make install копируются (если придерживаться умолчальной схемы) в каталог /usr/local/bin, документация - в /usr/local/share, и так далее. А все они закрыты для изменения кем бы то ни было, кроме root'а.
Так что, прежде чем начинать установку собранного пакета, следует озаботиться получением соответствующих полномочий командой
$ su
или, в некоторых случаях, даже
$ su -
которая приведет среду исполнения команды в соответствие с конфигурацией суперпользователя.
Впрочем, иногда полномочия администратора могут оказаться необходимыми и при сборке программы или ее конфигурировании. Типичный тому пример - сборка ядра Linux штатными средствами, в ходе которой задействуются скрипты, требующие root-доступа. Впрочем, это - тема отдельной беседы.
Вернемся к сборке "обычных", если так можно выразиться, пакетов. Все вышесказанное относилось к случаю сборки без ошибок на любом этапе. От каковых, однако же, никто не гарантирован. И что делать, если ошибки появляются?
В случае ошибки при сборке пакета перед пользователем, как обычно, появляется два выхода: а) бросить это занятие, попробовав отыскать и установить прекомпилированный вариант пакета, и б) разобраться в причинах ошибки и попытаться ее устранить.
Наиболее часто сообщение об ошибке возникает в ходе предварительного конфигурирования. И, в большинстве случаев, связано с нарушением зависимостей, жестких или "мягких" - обычно это можно понять, внимательно читая экранный вывод.
С нарушением жестких зависимостей все ясно - нужно установить пакет, от которого зависит собираемый, и все - вариантов тут не предлагается. Нарушения же "мягких", но тем не менее принятых разработчиком по умолчанию, зависимостей обычно можно избежать посредством явного указания опций конфигурирования - типа disable-FEATURE и --without-PACKAGE.
Встречаются и ситуации кажущегося нарушения зависимостей, когда в ходе конфигурирования следует сообщение об отсутствии пакета имя рек, хотя пользователь точно знает, что таковой был установлен. В одних случаях это может быть связано с тем, что собираемый пакет ссылается не просто на некую библиотеку, но на конкретную ее версию. При этом, даже если в системе установлена более новая ее реализация, заведомо перекрывающая функциональность предыдущей (а совместимость сверху вниз - один из краеугольных камней программирования вообще и POSIX-программирования - в особенности), в ходе конфигурирования будет отмечено нарушение зависимостей.
Разрешение такой коллизии - очень простое: создание символической ссылки вида
$ ln -s libname.xxx libname.yyy
где xxx - номер старой версии библиотеки, а yyy - актуальный ее вариант.
Другой случай - когда сценарий конфигурирования пакета ищет библиотеку, от которой он зависит, не в том каталоге, где она реально располагается. Так, старые приложения KDE могут ожидать требуемых им библиотек в каталогах типа /usr/local/qt и /usr/local/kde, тогда как ныне они, скорее всего, будут располагаться в ветвях каталога /opt.
И тут выход из положения не сложен. Во-первых, можно задать переменную окружения
LDPATH="/opt/qt:/opt/kde"
значения которой точно определяют каталоги соответствующих программных комплексов.
Во-вторых, эти значения можно просто указать в качестве опций конфигурационного скрипта:
$ ./configure --with-qt-dir=/opt/qt \ --with-kde-dir=/opt/kde
Реже бывают ошибки при исполнении команды make. Однако бороться с ними труднее. Общий рецепт тут дать очень трудно. Можно только посоветовать внимательно читать вывод сообщений о ходе компиляции, непосредственно предшествующих ее обрыву.
Ошибки при инсталляции связаны, почти всегда, с отсутствием прав на запись в каталоги, в которые помещаются устанавливаемые компоненты пакета. Иногда же они возникают вследствие того, что целевой каталог просто не существует. Например, в таких дистрибутивах Linux, как CRUX и Archlinux, из пакетов штатного комплекта изъята вся документация, кроме man-страниц. И, соответственно, отсутствуют каталоги для помещения документации в форматах info и html. А поскольку практически любая программа проекта GNU сопровождается info-документацией, попытка инсталляции ее вызывает ошибку. Побороть которую очень просто: нужно только уничтожить в дереве исходников соответствующие подкаталоги.
На какой бы стадии ни возникла ошибка, перед повторной сборкой пакета дерево исходников следует очистить от побочных продуктов сборки предыдущей. Резонные люди рекомендуют просто стереть каталог с исходными текстами и развернуть его из тарбалла по новой.
Это, конечно, самый радикальный способ, но не всегда приемлемый. Обычно можно обойтись и терапевтическими мерами. Потому что в большинстве пакетов предусматриваются специальные цели для таких процедур. Первая из них
$ make clean
Она вычистит дерево исходников от объектных модулей, собранных при предыдущей компиляции. Сохранив, тем не менее, созданные при конфигурировании Make-файлы. Если нужно избавиться и от них - существует цель
S make distclean
по исполнении которой дерево исходников теоретически должно приобрести первозданный вид.
Пакеты, как правило, устанавливаются для того, чтобы запускать входящие в них программы. Однако не исключено, что первый же запуск новой программы показывает, что она делает что-то не то или не так, нежели это нужно пользователю. Или просто ему не нравится. И возникает вопрос - а как удалить такой неподходящий пакет?
Можно, конечно, последовательно пройтись по всем каталогам, в которые записывались компоненты пакета, выявить их по каким-либо признакам (например, по атрибуту ctime, в данном случае отвечающему времени создания файла) и удалить вручную. Однако способ этот - трудоемкий и чреват ошибками.
К счастью, большинство разработчиков, не страдающих манией величия, предусматривают такую ситуацию, определяя специальную цель для удаления программы. Обычно это -
$ make uninstall
реже -
$ make deinstall
Любую из этих команд нужно дать в корне дерева исходников - именно поэтому его желательно сохранять и после сборки, несмотря на непроизводительный расход дискового пространства: иначе единственным способом удаления пакета останется ручной. При деинсталляции команда make отыскивает установленные компоненты пакета в дереве файловой системы и удаляет их.
Важно, что make uninstall не затрагивает пользовательских настроечных файлов - т.н. dot-файлов в его домашнем каталоге, которые часто генерируются автоматически при первом запуске программы. Такие файлы при необходимости в любом случае придется удалять вручную. Казалось бы - неудобство, однако сейчас мы увидим, что это не лишено резонов.
Дело в том, что самостоятельная сборка пакетов из исходников не предусматривает никакого механизма обновления их версий. Правда, на самом деле обладателю настольной машины обычно нет надобности гнаться за самыми актуальными версиями большинства пользовательских программ: это целесообразно делать только при существенном расширении их функциональности или обнаружении серьезных ошибок. Однако администратор системы вынужден отслеживать все обновления пакетов, связанные исправлением ошибок в безопасности системы. А поскольку, как я все время повторяю, каждый пользователь - это немного админ своего десктопа, необходимость в обновлении версий возникает достаточно часто.
Так вот, единственный способ обновления собственноручно собранной программы - это собрать и инсталлировать ее по новой. При этом теоретически старые компоненты должны затереться новыми. Однако на практике от старой версии могу остаться хвосты, не только бесполезные, но, возможно, и конфликтующие с файлами новой версии.
И потому при обновлении версий вручную обычно рекомендуется сначала удалить старую версию, и лишь затем инсталлировать новую. Предварительно убедившись, конечно, в успешности сборки ее (то есть выполнив стадии ./configure и make - это еще одна причина для обособления цели make install) и работоспособности (для чего можно пробно запустить исполняемый файл пакета прямо из каталога исходников).
В то же время при смене версий, как правило, желательно сохранить все выполненные ранее настройки пакета - подчас это весьма трудоемкая процедура. И вот тут-то и оказывается, что пользовательские настройки удаленной версии остались в неприкосновенности в домашнем каталоге./p>
Вопросы оптимизации
Сказавши "а", логично было бы добавить "б". А именно, после пересборки ядра - выполнить оптимизацию приложений под имеющееся "железо", по крайней мере - критически важных для производительности системы в данных условиях. Тем более что современные версии компилятора gcc предоставляют к тому много возможностей.
Выбор версии компилятора gcc вообще очень важен для целей оптимизации. До недавнего времени практически на равных существовали две его ветки - 2.95.X и 3.X. Причем преимущества второй были отнюдь не очевидны. Дело в том, что ветка 3.X на протяжении длительного времени развивалась в сторону удучшения генерации кода Си++, тогда как код, генерируемый из программ на чистом Си, по свидетельству знатоков, получался даже хуже, чем при использовании gcc-2.95.X. И потому эта версия рекомендовалась для сборки наиболее ответственных частей Linux- и BSD-систем, таких, как ядро, гланая системная библиотека и сам компилятор, не содержащие кода Си++. А это и есть первые претенденты на оптимизацию, не считая "тяжелых" приложений типа KDE или GNOME, а также мультимедийных и узкоспециальных программ. В BSD-системах gcc-2.95.X вообще использовался по умолчанию. Однако компилятор этой ветки не предусматривал возможности оптимизации под современные процессоры с их специализированными наборами инструкций (типа SSE и 3DNow!) - ибо появился задолго до них.
Положение изменилось с выходом gcc-3.4.X, который первым из своей линии генерировал Си-код не хуже предшественника - а подчас и лучше. И потому использование его видится предпочтительным для всех компонентов любых систем. Правда, он по сию пору входит не во все, даже весьма современные, дистрибутивы Linux, а из BSD-семейства штатно предусмотрен только в DragonFlyBSD, но это, вероятно, вопрос времени.
Оптимизация приложений достигается заданием соответствующих флагов - параметров компилятора gcc, определяемых при его вызове. Первый среди них - собственно уровень оптимизации, который задается флагом -O# и может варьировать от нулевого (флаг -O0, при котором никакой оптимизации не происходит) до максимального -O3.
Большинство пакетных дистрибутивов Linux, декларирующих свою оптимизированную природу, собирается, насколько мне известно, с флагом -O2. В BSD-семействе по умолчанию принята оптимизация уровня -O1 для всех ее компонентов - ядра, базовой системы и портов; более того, до недавнего времени выполнить операцию make world (полную пересборку системы) при более высоких уровнях оптимизации было практически невозможно.
Выбор уровня оптимизации - не столь однозначный вопрос, как может показаться. И максимально высокий уровень -O3 далеко не всегда дает лучшие результаты, а подчас производительность собранных с его использованием программ даже снижается. Не говоря уж о том, что некоторые программы вообще при этом уровне собираться отказываются. И в любом случае при уровне -O3 размер исполняемого кода оказывается больше, нежели при -O2, что ведет снижение скорости запуска приложений.
Воообще говоря, мои (и не только мои) наблюдения показывают, что самый большой скачек в быстродействии происходит при переходе от уровня -O0 (то есть отказа от оптимизации) к -O1. Флаг -O2 обычно обеспечивает лишь небольшой прирост скорости запуска и выполнения программ, а результаты применения флага -O3 неоднозначны. В частности, эксперименты с пересборкой ядра и "мира" DragonFlyBSD показали стабильное (хотя и очень маленькое численно) его оставание от уровня -O2.
Следующие флаги, весьма влияющие на производительность, задают конкретный процессор для целевой машины: -mcpu=значение или -march=значение. Различие между ними в том, что программа, собранная с флагом -mcpu, будучи оптимизированной под заданный в качестве значения "камень", сохраняет способность запуска на более младших моделях, тогда как флаг -march заоптимизирует программу до того, что она сможет запуститься только на указанном процессоре или более старшем.
Допустимые значения флагов -mcpu и -march зависят от версии компилятора gcc. Однако в современных версиях, входящих в состав большинства дистрибутивов (gcc от 3.3.X и выше) здесь могут быть заданы все используемые ныне процессоры семейства x86: pentium, pentium-mmx, pentiumpro, pentium2(а также 3 и 4), разнообразные athlon'ы (athlon просто, athlon-tbird, athlon-xp, athlon-mp), не считая всякой экзотики типа winchip etc.
Детали можно посмотреть в документации на наличную версию gcc.
Особенно богатые возможности для оптимизации под современные процессоры предсотавляет gcc-3.4.X. В нем имеется поддержка и AMD64, и Prescott с его набором новых инструкций и улучшенным HyperThreading), а главное, судя по имеющимся сообщениям, существенно выросло качество оной (в том числе и поддержка HT). И если раньше (в версиях 3.3.X), сборка, например, под Pentium-4 подчас давала худшие результаты, нежели чем под абстрактный i686, то теперь для процессоров от Intel имеет смысл подбирать наиболее близкое значение флага -march (для процессоров AMD точное указание процессора всегда давало хорошие результаты).
Следующие процессорно-специфичные флаги оптимизации позволяют задействовать специальные наборы инструкций современных "камней" - от MMX до 3DNow и SSE всех номеров. Для этого сначала определяем, куда в этих случаях должен обращаться процессор - к стандартному сопроцессору или соответствующим блокам специализированных инструкций. Делается это с помощью флага -mfpmath=значение, в качестве какового могут выступать 387 (стандартный сопроцессор) или sse (блок SSE-расширений); согласно документации, можно задать оба параметра, правда смысл этого остается мне не вполне ясным.
Насколько я понимаю, при компиляции под Athlon'ы более оправданным будет флаг -mfpmath=387, учитывая максимально мощный сопроцессор этого "камушка". А вот в случае с Pentium-III и, особенно, Pentium4 резонным выбором будет скорее -mfpmath=sse. К чему следует приложить еще парочку флагов - -mmmx плюс -msse (для "пятой трешки") или -msse2 (для "пятой четверки").
Впрочем, флаги для задйствования специальных наборов инструкций имеют смысл только для программ, которые в принципе способны их использовать. По имеющимся у меня сведениям, они дают хорошие результаты для некоторых видеоприложений и вообще всякого рода multimedia. Ожидать же от них выигрыша в быстродействии того же компилятора вряд ли оправданно.
Процессорно- привязанными флагами возможности оптимизации не исчерпываются. Есть еще флаги, специфическим образом обрабатывающие код для достижения максимальной производительности вне зависимости от типа процессора (вплоть до нарушений стандартов POSIX/ANSI, типа -ffast-math, благодаря чему теоретически можно собрать максимально быстрый код). Детальное их описание далеко выходит за рамки данной заметки. Тем более, что его можно найти в фундаментальном руководстве по gcc, написанном Ричардом Столлменом с соавторами (русский перевод доступен, например, на здесь - часть 1 и часть 2 - и номером его версии, весьма древним, смущаться не след, актуальности оно ничуть не потеряло).
Как уже говорилось, при использовании флагов оптимизации, особенно достаточно жестких (типа -O3 -march=значение, не говоря уже о -ffast-math), необходимо учитывать, что отнюдь не все программы гарантированно соберутся с ними. А те, что соберутся, вовсе не обязаны безукоризненно функционировать (даже, возможно, функционировать вообще - от ошибок сегментации гарантировать не может никто). А уж то, что при этом будет достигнут большой выигрышь в быстродействии - приходится только надеяться.
Я достаточно много экспериментировал с различными флагами оптимизации, в том числе и весьма жесткими, при разных версиях компилятора, и на разных системах (Gentoo, CRUX, Archlinux, FreeBSD 5-й ветви, DragonFlyBSD). И в конце концов пришел к весьма умеренному решению:
CFLAGS="-O2 -march=pentium4" CXXFLAGS="$CFLAGS"
Вероятно, рекордных результатов при нем не добиться, но оно дает гарантированно стабильный результат. По крайней мере, и ядро, и "мир" DragonFlyBSD (при gcc-3.4.X) собираются без проблем, как и большинство используемых мной портов.
Тем не менее, любители экстремальной сборки могут попробовать нечто вроде этого:
CFLAGS="-O3 -march=pentium4 \ -fomit-frame-pointer \ -funroll-loops -pipe \ -mfpmath=sse -mmmx -msse2" CXXFLAGS="$CFLAGS"
А при желании еще и подобрать специальные флаги оптимизации для программ Си++, чем я никогда не озадачивался вообще.
К слову сказать, флаги оптимизации задаются различными способами:
в командной строке при конфигурировании программы:
CFLAGS="значения" ./configure
в виде переменных окружения в данном сеансе шелла:
export CFLAGS="значения"
в sh-совместимы оболочках и
setenv CFLAGS "значения"
в csh и tcsh;
глобально, раз и навсегда, в профильном файле (например, в /etc/profile или /etc/cshrc);
в конфигурационном файле системы портов, если таковая используется (типа /etc/make.conf в BSD).
Осталось определить, что дает столь жесткая оптимизация? Компилятор gcc, пересобранный указанным мной ранее образом, на сборке ядра демонстрирует быстродействие на 10-15% выше, чем собранный по умолчанию. Учитывая, что на современных машинах ядро собирается минут за 10-15, казалось бы, немного. Однако при сборке монстров типа оконной системы Икс, интегрированной среды KDE или, скажем, OpenOffice, игра вполне стоит свеч. Да и прикладные программы, собранные с флагами оптимизации, работают существенно быстрее: Mozilla, для примера - чуть не в полтора раза, хотя для других Исковых программ столь высоких результатов получить не удается.