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

         

Атрибуты учетной записи


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

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

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

Так что имя пользователя в системе - условно. Более того, для системы по большому счету имя это абсолютно до лампочки. Потому что опознает она пользователя на самом деле не по нему, а по поставленному ему в соответствие числовому идентификатору (UID - User IDentificator). Как правило, это - просто порядковый номер пользовательской учетной записи, причем для обычного пользователя - начиная с некоего минимального числа. В одних системах это может быть 100, в других 500, в большинстве Linux-дистрибутивов - 1000. Младшие идентификаторы система резервирует для своих целей - для тех самых демонов и виртуальных юзеров, о которых говорилось ранее. И еще - идентификатор 0 всегда и везде в POSIX-мире бронируется для поминаемого выше всуе имени божьего - то есть для root'а.

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

Так почему же все-таки имя пользователя считается непременным атрибутом его акаунта? Да все потому, что лицо у Unix - человеческое. И создатели системы прекрасно понимали, что запомнить имя alv для пользователя гораздо проще, чем идентификатор 1276. Вот ему и пошли на встречу.

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

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

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



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

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

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


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

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

И последнее. Многопользовательская система предполагает не только защиту пользователей от зловредного влияния других пользователей, но и обмен данными между группой пользователей-товарищей. Которая так и называется - группой пользователей, или просто группой. И любой пользователь по умолчанию включается минимум в одну группу - может быть, свою собственную, не имеющую других членов. Хотя в большинстве Linux-дистрибутивов такая умолчальная группа обычно называется users и включает в себя всех реальных пользователей, кроме root'а (а в BSD, например, каждый пользователь по умолчанию приписывается к своей собственной, одноименной ему, группе). И имя этой группы - еще один непременный атрибут учетной записи пользователя.



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

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


Доступ к атрибутам


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

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

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

Итак, первый резонный вопрос юзера - кто я? Для ответа на него командуем:

$ echo $USER

где символ $ после команды echo и пробела и обозначает, что все следующие символы являются переменной, а USER - имя данной конкретной переменной. Так вот, на эту команду мы незамедлительно получим ответ:

alv

свидетельствующий, что давший ее пользователь носит имя (login) alv. Ответом на запрос о программе, выполняющей роль пользовательской среды

$ echo $SHELL

будет ее имя, вместе с путем к исполняемому файлу:

/bin/tcsh

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

$ echo $HOME /home/alv

где /home/alv - и есть домашний каталог пользователя alv (совпадение логина пользователя и имени его домашнего каталога - не обязательно, но, как правило, имеет место быть).

Однако наиболее полную информацию о себе пользователь может получить прямым просмотром базы пользовательских акаунтов.
База данных хранится в файле passwd каталога /etc (вопросы, что такое файл, каталог и почему его имя должно предваряться символом слэша - пока замнем для ясности, речь об этом будет в ). Пока для наших целей достаточно знать, что /etc/passwd - простой текстовый файл, доступный для чтения кому угодно - был бы инструмент для чтения. Забегая вперед, скажу, что, как и следовало ожидать, такой инструмент в POSIX-системах предоставляется, и даже не один. Хотя сейчас нам много не нужно - достаточно одного, наиболее удобного. Таковым в Linux выступает обычно команда less, а в BSD - еще и команда more (правда, как выяснится в , это одно и то же). И та, и другая требуют указания еще и имени просматриваемого файла. То есть в нашем случае это будет выглядеть так:

$ less /etc/passwd

или

$ more /etc/passwd

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

root:x:0:0:root:/root:/bin/zsh bin:x:1:1:bin:/bin: daemon:x:2:2:daemon:/sbin: mail:x:8:12:mail:/var/spool/mail: ftp:x:14:11:ftp:/home/ftp: nobody:x:99:99:nobody:/: alv:x:1000:100::/home/alv:/bin/zsh lis:x:1001:100::/home/lis:/bin/zsh

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

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

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


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

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

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

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

Пятое поле записи - так называемый комментарий. Для root'а и псевдопользователей здесь стоят некие условные значения, совпадающие с их именами. Что на деле отнюдь не обязательно - форма заполнения этого поля свободная. Так, для реальных пользователей здесь часто указываются их всамделишние ФИО по паспорту и любые другие данные, типа номера телефона. В приведенном примере для реальных пользователей "пункт пятый" просто оставлен пустым, так как своего имени и фамилии я пока не забыл. А во FreeBSD некоторые утилиты создания учетных записей пользователей обязательно потребуют придать этому полю какое-либо значение.

Шестое поле - домашний каталог пользователя. Для псевдопользователей тут опять же стоят некие непонятные имена - в некоторых случаях оно может быть и пустым.


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

И наконец, последнее поле записи - это имя той самой программы, которая выполняет роль среды обитания пользователя, запускаясь первой сразу же по его авторизации (опять несколько опережу события - в общем случае эта программа называется login shell). Можно видеть, что у псевдопользователей это поле пусто - они в такой среде не нуждаются. А вот root и тут проявляет свою человеческую сущность - и у него login shell имеется.

Вообще говоря, и для обычных пользователей заполнять седьмое поле записи не обязательно: в этом случае в качестве login shell для них будет запущена некоторая предопределенная по умолчанию среда (в POSIX-системах она всегда носит имя /bin/sh, хотя в реальности это могут быть разные программы).

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

$ less /etc/group

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

root::0:root ... wheel::10:root,alv,lis ...


users::100:alv,lis sound:x:101:alv,lis

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

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

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

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

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


Потому что особый статус членов группы wheel зависит не от ОС или дистрибутива, а устанавливается в файле /etc/login.access. Где и может быть отменен при желании.

К группе wheel в BSD-системах приписано большинство исполнимых файлов базовой системы, расположенных в каталогах /{bin,sbin} и /usr/{bin,sbin} (забегая вперед, отмечу, что фигурные скобки символизируют группировку имен, то есть /{bin,sbin} эквивалентно /bin и /sbin).

Некоторые файлы из каталогов /{bin,sbin} и /usr/{bin,sbin} (напомню, что владельцев всех их является пользователь root) имеют, однако, иную групповую принадлежность. Возникает вопрос, для чего они предназначены? Это станет понятным после рассмотрения понятия файла и его атрибутов. Пока же замечу, что, например, членство в группах dialer и network необходимо для использования модемного и сетевого соединения, а членство в группе operator дает пользователю возможность завершать работу машины соответствующими командами, в иных случаях требующими полномочий администратора. Напомню, что это относится к BSD-системам - в различных дистрибутивах Linux и группы другие, и назначение их может быть разным.

Осталось прояснить вопрос с паролями. Мы уже видели, что в файле /etc/passwd никаких паролей на самом деле нет. На вопрос "почему" легко ответить словами Балбеса из "Операции Ы" - чтоб никто не догадался. Ведь если мы, будучи простым пользователем, легко получили доступ к содержимому столь, казалось бы, важного файла, то и для злоумышленника это труда не составит. Ибо право чтения файла /etc/passwd имеет любой пользователь (хотя вносить изменения в большинство записей и полей может только root). И потому для хранения паролей в большинстве дистрибутивов Linux предусмотрено специальное потайное место - файл /etc/shadow.

Впрочем, просмотреть его сразу нам не удастся - ведь мы зашли в систему в качестве обычного пользователя, а ему доступ к /etc/shadow запрещен под любым соусом (в том числе и просто для чтения). Что делать?

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


Для этого предназначена специальная команда - su (что иногда трактуют как аббревиатуру от Super User, но на самом деле означает Set UID, потому что она позволяет получить права не только администратора, но и любого другого пользователя - достаточно указать его имя в качестве аргумента). Данная же без всякого аргумента, в самой простой форме, как

$ su

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

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

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

$ less /etc/shadow

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

Что же мы видим в нашем /etc/shadow? Да примерно такую же базу данных, что и раньше, о восьми полях. Первое поле - имя пользователя, реального или виртуального. Второе у псевдопользователей пусто. А у реальных (в том числе и у root'а) заполнено нечленораздельным набором символов. Это и есть пароль. Его представление в базе не имеет ничего общего с тем, что вводит пользователь при авторизации. Ибо пароль помещается в базу в односторонне зашифрованном виде. То есть по его представлению восстановить последовательность символов для ввода невозможно (как говорят эксперты по безопасности - невозможно в принципе; глядя на свой /etc/shadow, охотно им верю).


И односторонняя шифровка пароля - это первый эшелон обороны в Linux.

Если пароль нельзя расшифровать, то его можно подобрать. И представление зашифрованного пароля может помочь в этом черном деле. А потому доступ к файлу /etc/shadow закрыт для всех, кроме root'а - даже для просмотра. Что являет второй эшелон обороны Linux-системы.

Я не случайно в последних абзацах подчеркиваю, что речь здесь идет именно о Linux. Потому что такой механизм защиты (он называется механизмом теневых паролей - shadow passwords) принят именно в этой ОС (по крайней мере, в большинстве ее дистрибутивов). А вот в BSD-системах механизм теневых паролей не используется, а эшелонированная оборона достигается другим способом. Там в качестве дополнительного кольца защиты используется пара файлов: /etc/master.passwd и /etc/passwd. Первый из них, доступный только для root'а, содержит все данные о пользователе, включая его пароль, во втором, открытом для всеобщего обозрения, есть все то же самое - но без пароля.

К слову сказать, во FreeBSD оба эти файла существуют в двух вариантах - текстовом (собственно /etc/master.passwd и /etc/passwd), доступном для просмотра, и неудобочитаемом бинарном (/etc/master.spwd.db и /etc/spwd.db) Именно последние используются для идентификации пользователей: изменения, внесенные (скажем, в обычном редакторе) в текстовые варианты баз пользовательских акаунтов, должны быть соответствующим образом преобразованы. Что, кроме всего прочего, может рассматриваться как еще один эшелон обороны.

Завершая разговор о паролях, еще раз обратимся к файлу /etc/master.passwd из FreeBSD. Просмотр его показывает, что кроме полей, общих с файлом /etc/passwd, в нем можно видеть еще три. Они хранят информацию о времени последнего изменения пароля, а также могут определять всякие дополнительные сроки действия пароля, Например, здесь можно задать дату, когда пароль должен быть изменен, или дату, после которой вход по этому паролю в систему невозможен. Неконец, очень важное поле class (например, russian) позволяет определить некоторые свойства, общие для ряда пользователей.


Все для блага человека: пользовательские акаунты


Следующими из наипервейших "вечных истин" POSIX-систем являются понятия файла, процесса и пользователя. Это - те три кита, на которых зиждется дивный POSIX-мир. Всё, что существует в системе статически, суть файлы; всё, что существует в системе в динамике, суть процессы; все, кто тем или иным образом взаимодействует с системой - ее процессами и ее файлами, - суть пользователи.



Интермедия: средства управления акаунтами


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

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

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

$ adduser

В ответ она в диалоговом режиме последовательно запросит следующую информацию:

Username - имя пользователя, требуется задать обязательно;

Full name - теоретически имя и фамилия пользователя, его можно оставить пустым или ввести произвольную информацию, которая составит значение поля комментария в базе данных пользователей; Uid - идентификатор пользователя; если это поле оставить пустым, то он автоматически примет ближайшее свободное значение (для первого пользователя в системе - 1001); Login group - основная группа пользователя, по умолчанию - собственная, совпадающая с его именем; Login group is group_name. Invite exp into other groups? [] - включить ли пользователя в другие группы; если предполагается его доступ к полномочиям администратора, то здесь следует указать группу wheel; Login class [default] - очень важный атрибут (см. следующую интермедию); в наших условиях лучше всего задать класс russian; Shell (sh csh tcsh) [sh] - пользовательская оболочка; предлагаемый по умолчанию вариант (sh) - отнюдь не лучший, о чем подробнее я скажу чуть позже; Home directory [/home/username] - имя домашнего каталога пользователя; Use password-based authentication? [yes] - использовать ли авторизацию по паролю; Use an empty password? (yes/no) [no] - использование пустого пароля (по нажатию клавиши Enter); Use a random password? (yes/no) [no] - генерация произвольного пароля; Enter password - ввод пароля; Enter password again - и его повторение; Lock out the account after creation? [no] - закрыть ли доступ к акаунту после его создания.


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

Username : exp Password : ***** Full Name : Uid : 1002 Class : russian Groups : exp Home : /home/exp Shell : /bin/sh Locked : no OK? (yes/no):

Если с этим согласиться. последует сообщение об успехе процедуры и предложение создать еще один Акаунт:

adduser: INFO: Successfully added (exp) to the user database. Add another user? (yes/no):

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

$ adduser -C

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

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

Enter additional groups -список дополнительных групп, к которым будут приписаны все вновь создаваемые пользователи (например, wheel operator);

Login class - класс пользователей по умолчанию, взамен defaults; как я уже говорил, в качестве такового в наших условиях целесообразно определить russian;

Shell (sh csh tcsh) - командная оболочка (login shell) по умолчанию; если вы предпочитаете пользоваться каким-либо шеллом, здесь не указанным (например, zsh), его также можно указать здесь, внеся предварительно имя и полный путь к нему в файл /etc/shells;

Home directory - если планируется использование домашних каталогов, отличных от умолчальных /home/username;

Use password-based authentication? и прочие - общие условия парольного доступа;

умолчальное согласие с введенными данными и (или) их правка.

Определенные таким образом общие атрибуты сохраняются в файле /etc/adduser.conf и введенные нами ответы на их запрос в дальнейшем будут предлагаться по умолчанию.

К сказанному остается добавить только, что команда adduser автоматически сама проверяет имя и идентификаторы пользователя на предмет уникальности, предлагая для последних подходящие значения (обычно - следующие по порядку за ID предыдущего зарегистрированного пользователя), создает домашний каталог пользователя и копирует туда "скелеты" конфигурационных файлов (они берутся из файлов каталога /etc/skel - по умолчанию он пуст, и созданием соответствующих "скелетов" следует озаботиться самому), после чего делает соответствующие записи в базах данных пользователей - файлах /etc/passwd, /etc/master.passwd, /etc/group и /etc/master.group.



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

$ man 8 adduser

Команда adduser (или useradd) имеется и в Linux, однако там возможности ее несколько меньше. В частности, она не создает автоматически домашний каталог пользователя - это следует сделать вручную командой

$ mkdir /home/username

Да еще и позаботиться о установке его принадлежности пользователю и группе:

$ chown -R username /home/username && \ chgroup -R users /home/username

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

$ chown -R username:users /home/username

Использование adduser - наиболее автоматизированный, но и наименее гибкий способ управления учетными записями. Основное его назначение - создание либо единичного акаунта, либо многочисленных учетных записей со сходными атрибутами и профильными файлами пользователей, "скелеты" которых заблаговременно создаются в каталоге /etc/skel.

Другая крайность - внесение всех данных пользователя в файл /etc/master.passwd вручную (в текстовом редакторе) и дальнейший запуск команды pwd_mkdb, которая в форме

$ pwd_mkdb -p /etc/master.passwd

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

Далее, существует программа vipw, совмещающая оба процесса: вызов текстового редактора для внесения вручную изменений в файл /etc/master.passwd, а по выходе из него - модификацию баз пользовательских записей командой pwd_mkdb.


В качестве редактора используется значение переменной EDITOR в профильном файле администратора. Если такового нет - вызывается классический редактор всех Unix-систем - vi (откуда и название программы).

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

$ pw useradd username

после чего она сама подберет подходящие (то есть ближайшие по порядку свободные) идентификаторы пользователя и группы, поля общих сведений, домашнего каталога и командной оболочки заполнит некими значениями по умолчанию (User ID, /home/username, /bin/sh, соответственно), прочие же оставит пустыми. Сам домашний каталог она тоже не создаст - для этого она потребует формы

$ pw useradd username -m

во исполнение которой заодно будут скопированы и "скелетные" файлы.

Кроме этого, команда pw может создавать учетную запись пользователя с заранее предопределенными атрибутами, описанными в файле /etc/pw.conf. Правда, для этого такой файл необходимо предварительно создать - командой

$ pw useradd -D

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

$pw useradd -D username

Ничто не мешает и задать несколько таких конфигурационных файлов (например, /etc/pw1.conf, /etc/pw2.conf и т.д.) для создания учетных записей пользователей разных категорий (например реальных или виртуальных, или пользователей с различными значениями атрибута class).


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

$ pw adduser username -C /etc/pw1.conf

она создает учетную запись нового пользователя с именем newuser и его домашним каталогом /home/newuser, в который копируются конфигурационные файлы командной оболочки. Файлы, подлежащие копированию, определяются содержимым каталога /etc/skel и могут быть отредактированы администратором в текстовом редакторе.

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

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

Администратор системы с помощью команды

$ passwd username

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

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

#Changing user database information for alv. Shell: /bin/csh Full Name: Alex Fedorchuk Office Location: Office Phone: Home Phone: Other information:

По завершении их модификации и выходе из редактора внесенные изменения будут зафиксированы в базах данных автоматически.


Нетрудно догадаться, что для этого команда chpass должна иметь упоминавшийся в атрибут "суидности", в чем можно убедиться посредством:

$ ls -l /usr/bin/chpass -r-sr-xr-x 6 root wheel 32324 Jan 28 16:12 /usr/bin/chpass

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

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

$ chpass username Login: alv Password: lhBESi9NHbsrg Uid [#]: 1000 Gid [# or name]: 1000 Change [month day year]: Expire [month day year]: Class: russian Home directory: /home/alv Shell: /bin/csh Full Name: Alex Fedorchuk Office Location: Office Phone: Home Phone: Other information:

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

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

Теоретически, для изменения учетных записей в разных POSIX-системах применяются также команды chfn и chsh, но в BSD эти файлы - лишь иные имена для команды chpass, и потому вызов любой из них автоматически влечет запуск последней.



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

$ adduser -shell shell

для изменения командной оболочки,

$ adduser -class login_class

для изменения класса пользователя, и т.д. - список возможностей можно получить по команде

$ adduser --help

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

$ man 8 pw

Удаление учетной записи пользователя, на первый взгляд, задача очень простая: достаточно удалить соответствующую строку из файла /etc/master.passwd и запустить команду pwd_mkdb для синхронизации изменений, точно также, как это делалось при создании акаунта. Или прибегнуть к программе vipw, которая сделает это сама. Однако и в том, и в другом случае будут удалены только сами учетные записи, а пользовательские каталоги, почтовые ящики, временные файлы, результаты всякого рода протоколирования действий (log-файлы) благополучно сохраняться, причем будут рассеяны по ряду каталогов (кроме /home, в основном, также по /tmp и /var). Кроме того, упоминания имени удаленного пользователя останутся в списках принадлежности к группам, конфигурационных файлах авторизации, систем доставки почты, почтовых и ftp-клиентов (и в иных местах). Что может создать проблемы, если в дальнейшем будет создан пользователь с тем же идентификатором и (или) именем.

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

$ find / -user username

А добавив в конец строки опцию -delete (или перенаправив вывод по конвейеру команде rm) - и немедленно уничтожить их. А для поиска упоминаний имени в конфигурационных файлах можно прибегнуть к команде grep:

$ grep -e username /*

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


И может занять немало времени.

Поэтому может оказаться целесообразным прибегнуть к команде rmuser для автоматизации процесса. В форме

$ rmuser username

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

И конечно, ту же работу в состоянии сделать и палочка-выручалочка администратора - команда pw в форме

$ pw userdel username -r

Где следует обратить внимание на опцию -r - без нее pw удалит только пользовательскую запись из базы акаунтов.

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

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

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

И конечно, для управления группами можно применить универсальное средство - pw. В форме

$ pw group add groupname

она создаст новую группу,

$ pw group del groupname

удалит существующую, а посредством

$ pw group mod groupname -m username1 username2

или

$pw group mod groupname -M username1 username2

дополнит или полностью перепишет списков членов группы, соответственно. Наконец, с помощью

$ pw usermod username -G groupname

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


О себе, любимом


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

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

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

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



Очередная преамбула


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

Получается нечто вроде известной сказки про белого бычка, которая, как уже говорилось, в мире Unix задумчиво называется рекурсией. И определить понятие процесса без представления, что такое файл и пользователь, столь же трудно, как определить понятие файла, не имея представления о процессе. Однако мы, POSIX'ивисты, не боимся трудностей и, подобно Александру Филипповичу (Македонскому) разрубим этот узел, чисто волюнтаристически установив в нем точку отсчета. В качестве каковой резонно было бы принять себя, любимого, сиречь пользователя.



Создание и изменение акаунтов


Таким образом мы плавно подошли к вопросу о том, как создаются и изменяются пользовательские акаунты. Или, иными словами, кто определяет пользователей системы, и каким образом.

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

Правда, пользователь root действительно существует в системе от века, аналогично Господу Богу. Вернее, пользователь с UID 0 - как уже говорилось, сама по себе система об именах пользователей не имеет никакого понятия - за их соответствие идентификаторам отвечает главная системная библиотека glibc, И в процессе установки Linux возможна ситуация (а иногда она и действительно возникает - например, в ходе самостоятельной сборки from Scratch), когда система еще не догадывается о существовании пользователя с именем root. И что же? Она прекрасно справляется с его распознаванием администратора по его нулевому идентификатору, наделяя соответствующими полномочиями.

Все же прочие пользователи являются порождением нашего бога из машины - root-оператора. Причем косвенно это относится даже к псевдопользователям-демонам (почему - станет ясно со временем). А уж обычные реальные и виртуальные (почтовые и ftp-клиенты) пользователи порождаются root'ом непосредственно.

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




Атрибуты процесса


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

идентификатор процесса (PID);

идентификатор родительского процесса (PPID);

имя хозяина процесса;

идентификатор хозяина процесса (UID);

идентификатор группы хозяев (GUID);

приоритет;

терминал.

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

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

Для рассмотрения смысла остальных атрибутов нам придется обратиться к команде ps, которая и выводит информацию о процесса в виде, доступном пониманию пользователя.

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

Итак, сразу после авторизации пользователя в ответ на команду ps -u будут выведены две строки, состоящие из нескольких колонок, в числе которых - следующие:

USER PID COMMAND alv 5408 -zsh alv 5598 ps -u

Первая колонка указывает на имя хозяина процесса - как правило (хотя и не обязательно - и позднее можно будет видеть, что это важно), таковым выступает пользователь, этот процесс запустивший.
Вторая колонка - идентификатор процесса, третья - имя программы, породившей процесс; очевидно, что в момент сразу после авторизации это будет только командная оболочка - -zsh (символ дефиса перед именем показывает, что это - не просто некая программа, а именно login shell: завершение ее эквивалентно окончанию сеанса работы данного пользователя). Ну и, конечно, сама команда ps - ей ведь тоже соответствует свой процесс, хотя и завершившийся. Так вот, именем хозяина определяются отношения процесса к файлам и иным процессам. То есть процесс получает те же привилегии, которые имеет его хозяин.

Сказанное относительно принадлежности процессов относится не только к интерактивным пользовательским процессам, но и к демонам. Хотя их, казалось бы, никто не запускает, но и они имеют своих хозяев. Это - те самые псевдопользователи, о которых я упоминал в .

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

root 5617 passwd

Как могло получиться, что обычный, непривилегированный, пользователь запустил процесс, хозяином которого оказался root? Это станет понятно из дальнейшего. Пока же скажу, для чего это нужно. Ясно, что любой пользователь должен иметь возможность сменить свой пароль. Однако для этого ему придется внести изменения в базу пользовательских акаунтов, а файл /etc/passwd, как мы помним, открыт для изменения только администратору. И потому пользовательский процесс, соответствующий команде passwd, наделяется правами, в обычной жизни этому пользователю недоступными.

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


эффективный идентификатор "хозяина" (EUID), который собственно определяет привилегии данного процесса. Обычно UID и EUID совпадают - но бывают и исключения из этого правила. Если посмотреть их с помощью команды ps -l для процесса zsh, в поле UID мы увидим цифру 1000 (в данном случае - это UID юзера alv). Однако для процесса passwd поле это будет содержать значение 0, то есть эффективный идентификатор его унаследован не от пользователя, а от root-оператора.

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

Следующий атрибут процесса - его относительный приоритет (NICE), обозначаемый по английски словосочетанием nice value (что интерпретируется обычно как степень "дружелюбия" или "тактичности" по отношению к другим процессам). Он варьирует в диапазоне от -20 (минимальное "дружелюбие", то есть высший приоритет) до +20 (максимальное "дружелюбие", соответствующее низшему приоритету). Прикладные процессы в момент своего рождения получают приоритет 0 (то есть некую "среднюю" степень "дружелюбия").

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

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


Процесс пошел


Над нами солнце встает,

Процесс сам по себе идет...

Тимур Шаов



Понятие процесса


Понятие процесса принадлежит к тем сущностям, кажущимся интуитивно ясными, и которым, тем не менее, нелегко дать строгое (и при этом удобопонятное) определение. Наиболее распространенное их них - это представление процесса как программы в стадии ее выполнения (Андрей Робачевский. Операционная система Unix. СПб: БХВ-Петербург, 2000).

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

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

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

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



Разновидности процессов


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

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

Процессы getty и login относятся уже к пользовательским процессам. Которые, в отличие от системных, всегда ассоциированы с неким исполняемым файлом. Для процесса getty это будет, например, /usr/libexec/getty, а для процесса login - нечто вроде /usr/bin/login.

Процессы типа getty и login относятся к категории неинтерактивных, то есть тех, которые не привязаны к какому-либо терминалу, и на которые пользователь не может влиять непосредственно (общение с ними возможно только посредством т.н.
сигналов, о которых будет говориться несколько позже). Неинтерактивные процессы именуются иногда демонами (daemon). Термин этот не несет в себе мистического смысла, представляя простую аббревиатуру (Disk And Execution MONitor). Они инициируются самой системой при ее загрузке обычным образом (запуском соответствующих программ) и функционируют в фоновом режиме, активизируясь при необходимости совершения некоего действия - печати, доставки почты и т.д. Или, как в случае с login - авторизации пользователя.

Есть еще и процессы интерактивные, отличающиеся тем, что привязаны к некоторому терминалу, через который пользователь может с ними взаимодействовать. Так, авторизовавшись в системе, пользователь (реальный - о виртуальных далее речи не будет) запускает тем самым свой первый процесс - свою "умолчальную" командную оболочку (login shell; в Linux это скорее всего bash, в BSD-системах - /bin/sh или /bin/tcsh). Это - процесс интерактивный, он привязан к определенному терминалу, на котором авторизовался пользователь. И последний может с ним взаимодействовать определенным образом (вводить команды, например).


Управление процессами


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

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

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

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

$ kill -15 ###

где ### - номер (PID) убиваемого процесса, а 15 - численное значение для сигнала TERM (от terminated). Соответственно, команда эта может быть дана и в форме

$ kill -TERM ###

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

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

$ kill -9 ###

или

$ kill -KILL ###

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


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

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

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

Делается это двояко. Если требуется запустить программу с приоритетом, отличным от обычного, используется команда nice с величиной изменения nice value в качестве опции (предваряемой дефисом) и именем программы в качестве опции. Так, команда

$ nice -5 joe

запустит редактор joe с приоритетом, уменьшенным на пять единиц. Если же опустить опцию, приоритет уменьшится на десять единиц. Для увеличения приоритета администратор (и только он) может дать команду

$ nice --7 joe

что приведет к росту приоритета (то есть уменьшению "дружелюбия") на семь единиц.

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

$ renice 7 735

данная от лица пользователя - владельца процесса с PID 735, приведет к установке для него nice value, равного 7 (при условии, что прежний приоритет этого процесса был выше, например, 3 или 0). Администратор же может понизить приоритет того же процесса до значения nice value, равного 2, с помощью команды

$ renice 2 735

или присвоить ему максимальный приоритет:

$ renice -20 735

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


Жизнь и смерть процесса


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

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

$ps l

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

$ ps l | grep v1

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

UID PID PPID COMMAND 0 491 1 /usr/libexec/getty

То есть мы имеем в этой консоли единственный процесс getty, принадлежащий суперпользователю (UID 0), имеющий идентификатор 491 и порожденный процессом init (пример приведен для DragonFlyBSD, и потому идентификатор его равен единице).

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

UID PID PPID COMMAND 0 491 1 login 1001 1949 491 -zsh

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

И еще прошу обратить внимание: PID процесса, ассоциированного с командой login, тот же, что и для процесса, исполнявшего перед этим команду getty, Это свидетельствует, что авторизация пользователя не привела к запуску нового процесса - напротив, замещение одной программы другой произошло в рамках одного и того же процесса (одна из причин, почему не следует отождествлять процесс и программу).
А вот идентификатор для zsh - другой: командная оболочка исполняется в порожденном (см. значение PPID) процессе.

Вытеснение одной программы другой в рамках единого процесса - типичный способ запуска новой программы. Так, если из командной строки zsh мы запустим, например, текстовый редактор joe, то в ответ на

$ ps l | grep v1

увидим следующую картину:

UID PID PPID COMMAND 0 491 1 login 1001 1949 491 -zsh 1001 2208 1949 joe

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

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

В свою очередь, новый процесс также может выступать в качестве родительского. Так, редактор joe обладает способностью запуска из своей среды программ оболочки. В образовавшемся имитаторе командной строки можно запустить какую-либо другую программу, скажем, поиск файла командой find. Если в процессе поиска посмотреть распределение процессов (например, с другой виртуальной консоли), можно будет увидеть, что процесс joe породил как бы свою копию - но уже с иными идентификаторами (при этом PPID копии, как и следовало ожидать, равен PID оригинала).

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


Архивация и компрессия


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

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

Для архивации и компрессии предназначены самостоятельные команды. Хотя архивацию и компрессию можно объединить в одной конструкции или представить так, будто они выполняются как бы в едином процессе.

Традиционные средства архивации Unix-систем - команды cpio и tar. Суть первой, как можно понять их названия - копирование файлов в файл архива и из файла архива. Используется она в трех режимах.

Первый режим, copy-out, определяемый опцией -o (или --create), предусматривает считывание списка файлов (name list) со стандартного ввода и объединяет их в архив, который может быть направлен в архивный файл или на устройство для записи резервных копий. Список файлов для архивирования может представлять собой вывод какой-либо иной команды. Так, в примере

$ find ./* | cpio -o > arch.cpio

файлы текущего каталога, найденные командой find, при посредстве команды cpio будут направлены в архивный файл arch.cpio.

Второй режим (copy-in, опция -i, или --extract) осуществляет обратную процедуру: развертывание ранее созданного архива в текущем каталоге:

$ cpio -i < arch.cpio

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

В третьем режиме (copy-pass, опция -p, или --pass-through) команда cpio выполняет копирование файлов из одного дерева каталогов в другой, комбинируя режимы copy-out и copy-in, но без образования промежуточного архива. Список файлов для копирования (name list) считывается со стандартного ввода, а каталог назначения указывается в качестве аргумента:

$ cpio -p dir2 < name_list

Команда cpio имеет множество опций, позволяющих создавать, в частности, архивы в различных форматах (для межплатформенной переносимости). Однако я на них останавливаться не буду, отсылая заинтересованных к соответствующей man-странице: она не кажется мне удобной в применении. И упомянута здесь, во-первых, для полноты картины, во-вторых - универсальности ради (архивы cpio понимаются абсолютно всеми Unix'ами), в третьих - как одно из средств преобразования пакетов, используемых в различных дистрибутивах Linux, друг в друга. Например, утилита rpm2cpio преобразует широко распространенный формат пакетов rpm в еще более универсальный cpio.

Основным же средством архивирования во всех Unix-системах является команда tar. Обобщенный формат ее -

$ tar [options] archiv_name [arguments]

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

Главные опции и указывают на то, какие действия следует выполнить над архивом в целом:

создание архива (опция c, -c или --create);

просмотр содержимого существующего архива (опция t, -t или --list);



распаковка архива (опция x, -x, --extract или --get).

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

r (или --append) - добавление новых файлов в конец архива;

u (или --update) - обновление архива с добавлением не только новых, но и модифицированных (с меньшим значением атрибута mtime) файлов;

-A (--catenate или --concatenate) - присоединение одного архива к другому;

--delete - удаление именованных файлов из архива;

--compare - сравнение архива с его источниками в файловой системе.

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

Проиллюстрируем сказанное несколькими примерами. Так, архив из нескольких файлов текущего каталога создается следующим образом:

$ tar cf arch_name.tar file1 ... file#

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

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

$ tar cvf arch_name.tar *

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


А командой

$ tar cvf arch_name.tar dir

каталог dir будет упакован с полным сохранением его структуры.

С помощью команды

$ tar xvf arch_name.tar

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

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

$ tar xvf arch_name.tar filename

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

$ tar tf arch_name.tar

Если архив собирался по первой схеме (с именами файлов в качестве аргументов, вывод ее будет примерно следующим:

dir2/ dir2/file1 example new newfile tee.png

При втором способе архивации мы увидим на выводе нечто вроде

dir1/ dir1/example dir1/new dir1/newfile dir1/tee.png dir1/dir2/ dir1/dir2/file1

В данном примере опция v была опущена. Включение ее приведет к тому, что список файлов будет выведен в длинном формате, подобном выводу команды ls -l:

drwxr-xr-x alv/alv 0 10 май 11:03 2002 dir2/ -rw-r--r-- alv/alv 0 10 май 11:03 2002 dir2/file1 ...

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

Команд для компрессии файлов несколько, но реальный интерес ныне представляют две парные утилиты - gzip/gunzip и bz2/bunzip2.


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

Команда gzip - это традиционный компрессор Unix-систем, сменивший в сей роли более старую утилиту compress. Простейший способ ее использования -

$ gzip filename

где в качестве аргументов будет выступать имя файла. При этом (внимание!) исходный несжатый файл подменяется своей сжатой копией, которой автоматически присваивается расширение *.gz.

В качестве аргументов может выступать и произвольное количество имен файлов - каждый из них будет заменен сжатым файлом *.gz. Более того, посредством опции -r может быть выполнено рекурсивное сжатие файлов во всех вложенных подкаталогах. Подчеркну, однако, что никакой архивации команда gzip не производит, обрабатывая за раз только единичный файл. Фактически форма

$ gzip file1 file2 ... file#

просто эквивалент последовательности команд

$ gzip file1 $ gzip file2 ... $ gzip file#

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

Команда gzip имеет и другие опции, указываемые в краткой (однобуквенной) или полной нотации. В отличие от tar, знак дефиса (или, соответственно, двойного дефиса) обязателен в обоих случаях. Так, опциями -1 ... -9 можно задать степень сжатия и, соответственно, время исполнения процедуры: -1 соответствует минимальному, но быстрому сжатию, -9 - максимальному, но медленному. По умолчанию в команде gzip используется опция -6, обеспечивающая разумный компромисс между скоростью и компрессией.

Благодаря опции -d (--decompress) команда gzip может выполнить развертывание сжатого файла, заменяя его оригиналом без расширения *.gz. Хотя в принципе для этого предназначена команда gunzip:

$ gunzip file.gz

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



В последнее время широкое распространение получил компрессор bzip2, обеспечивающий большую (на 10-15%) степень сжатия, хотя и менее быстродействующий. Использование его практически идентично gzip, с деталями его можно ознакомиться с помощью страницы экранной документации man bzip2. Итоговый компрессированный файл получает имя вида *.bz2 и может быть распакован командой bunzip2 (или командой bzip2 -d). Следует только помнить, что форматы *.gz и *.bz2 не совместимы между собой. Соответственно, первый не может быть распакован программой bunzip2, и наоборот.

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

$ tar cvzf dir.tar.gz dir/

Обратите внимание, что суффикс *.gz в этом случае нужно указывать в явном виде - автоматически оно к имени архива не присоединяется и компрессированный архив будет иметь вид dir.tar. Поскольку в Unix расширения имен файлов не играют той сакральной роли, что в MS DOS, это не помешает распаковке такого файла командой

$ tar xvzf dir.tar

Опция z сама по себе никакой компрессии не выполняет - она просто вызывает компрессор gzip для сжатия каждого из архивируемых файлов. Аналогичный смысл имеет и опция j - только ею для этой цели привлекается команда bzip2 (в некоторых системах для вызова последней из команды tar используется опция y).

При использовании команды tar с опцией z (или j) исходные файлы остаются в неприкосновенности. Следует, однако, помнить, что архив сжатых файлов не может быть обновлен командой tar с параметрами r или u.

Есть и другой способ совместной архивации и компрессии - просто последовательность команд

$ tar cf dir.tar * $ gzip dir.tar

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

Компрессированные архивы, созданные сочетанием программ tar и gzip/bzip2 - общепринятый в Unix-системах метод распространения файлов. Однако иногда для совместимости с ОС, не допускающими двух точек в имени файла (знаете такую ОС?), компрессированным tar-архивам присваивается суффикс *.tgz. Можно встретить и файлы с маской *.tbz2 (или даже *.tbz - именно такой вид имеют пакеты в 5-й ветке FreeBSD). Нетрудно догадаться, что это те же архивы *.tar.bz2.


Атрибуция


К слову сказать - об атрибутах, следующая группа команд предназначена именно для атрибуции файлов. В ней - chmod, chown, chgrp, umask, а также уже затронутая ранее команда touch.



Еще раз об именах


Как можно заключить из изложенного выше, имя файла не несет в POSIX-системах того сакрального смысла, который вкладывался в него со времен DOS. Не имеется здесь и расширений имен (типа приснопамятной DOS-маски 8.3), предопределяющих отношение ОС к данным из конкретного файла.

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

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

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

Однако практически на имена файлов накладывают свои ограничения еще и некоторые прикладные программы. В первую очередь это - командные оболочки, которые целый ряд символов, не принадлежащих к числу алфавитно-цифровых, интерпретируют по-своему - как коды управления, регулярные выражения, и так далее. В главе о командах будет показано, что в принципе специальное значение символов можно отменить. Тем не менее, такие знаки, как *, !, #, @ и прочие из верхнего ряда клавиатуры, за исключением подчеркивания (_), не рекомендуется включать в имена файлов. К слову, сам символ отмены специального значения - обратный слэш (\), - также в именах лучше не использовать...

Зато точек имя Unix-файла может содержать сколько угодно - ведь мистического значения (отделения собственно имени от расширения) в этот символ не вкладывается. И потому здесь обычными являются имена типа archive.tar.gz или archive.tar.bz2 - файлы архивов, образованных утилитой tar и компрессированных программами gzip или bzip2, соответственно (в Unix'ах понятия архивации и компрессии суть вещи, обычно, разные).


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

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

Абсолютный путь отсчитывается от "главного" корневого каталога файловой системы вообще, и имеет следующий вид:

/dir1/filename

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

../dir2/filename

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

На полную длину пути также накладываются системные ограничения. Впрочем, они весьма мягкие - верхний лимит pathname лежит обычно в районе нескольких тысяч символов (в Linux, например, он составляет 4096 символов). Так что путь к имени файла может быть сколь угодно длинным - в разумных пределах, разумеется. А вполне возможная в Windows (точнее, в одной из ее файловых систем - vfat) ситуация, когда создается файл, доступ к которому в дальнейшем оказывается невозможным вследствие превышения лимита длины пути, в Unix практически исключена.

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


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

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

$ ./filename

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


Файлы устройств


Файлы устройств соответствуют различным присутствующим в системе устройствам. Устройства эти могут быть реальными (жесткие диски, принтеры и т.д.), а могут - т.н. псевдоустройствами, с которыми не ассоциировано никакого "железа" (например, знаменитое устройство /dev/null, не содержащее ничего, или /dev/zero, содержимое которого - сплошные нули).

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

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

Для файлов устройств, кроме обычной идентификации, есть и собственная система опознавания: по номерам - основному, или старшему (major), определяющему класс устройств (например, 12 - старший номер устройств, относимых к классу виртуальных консолей; это - для DragonFlyBSD, в иных системах старший номер того же устройства будут иными), и дополнительному, или младшему (minor). Последний является просто порядковым номером данного устройства в своем классе (например, для первой виртуальной консоли он будет равен 0, для второй - 1, и так далее).

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

В то же время некоторые на самом деле задействованные устройства могут не иметь ассоциированных с ними файлов. В этом случае пользователь вынужден пополнять их список, для чего предназначены специальная команда (mknode) и построенный на ее основе сценарий (/dev/MAKEDEV.

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

Понятие файла устройств в ОС Unix-семейства позволяет пользователю (и программам) общаться с железом, не вдаваясь в подробности того, как оное устроено. В разделе о командах будет показано, что вывод программ может быть перенаправлен в произвольный файл. Или, напротив, содержимое файла может быть считано в качестве входных данных. А поскольку устройства - это такие же файлы, то понятие перенаправления распространяется и на них. То есть для распечатки некоего текста его достаточно перенаправить в файл печатающего устройства (например, /dev/lpr - принтер на параллельном порту).


Файл как он есть


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



Интермедия: управление файлами


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



Как уже говорилось, все, что существует


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

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

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

Первая часть файла - так называемая область метаданных, именуемая по английски inode (index node или information node - мне встречались обе расшифровки). Нормального русского эквивалента для этого термина не придумано (выражения типа "информационный узел" вряд ли прояснять существо дела, а "индексный дескриптор" назвать русским выражением трудно), и потому далее он будет использоваться без перевода. В inode содержатся некоторые сведения о файле (именуемые атрибутами файла), как то:

идентификаторы файла и устройства, на котором он расположен;

тип файла;

счетчик ссылок;

его размер;

атрибуты принадлежности, доступа, режима и времени.

Все эти характеристики файла будут рассмотрены в следующих параграфах этого раздела.

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

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

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

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

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

Так вот, нумерация файлов в каждом таком разделе - независима от других, начинаясь с 2 (идентификатор "локального" корня). И потому в каждой ветви файловой системы могут быть (и неизбежно будут) файлы с одинаковыми идентификаторами. Чтобы отличать их друг от друга, и введен такой атрибут - идентификатор устройства.



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

Для этой цели предназначены т.н. файловые дескрипторы, которые не следует путать с дескрипторами индексными (сиречь inodes). Файловые дескрипторы (в дальнейшем условимся называть их дескрипторами просто) - это также числовые идентификаторы, которые присваиваются файлам по мере открытия их данным процессом. Причем нумерация их для каждого процесса - отдельная. То есть файл с inode xxx, открытый процессом A, получит дескриптор M, а в процессе B ему же будет присвоен дескриптор N.

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


Каналы и сокеты


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



Каталоги


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

В русскоязычной литературе по DOS/Windows каталоги обычно именуются директориями, а в последнее время в обиход вошел термин "папка" (по английски folder). Однако для Unix-систем термин "каталог" предпочтительнее, и я сейчас постараюсь объяснить, почему.

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

Роль каталогов трудно переоценить. Потому что имя файла в файловой системе Unix'ов существует только в составе каталога, и никак иначе. Собственно же содержимое файла ставится в соответствие имени через идентификатор его inode, подобно тому, как это осуществляется в базах данных. Механизм соотнесения имени файла, его метаданных и данных носит название жесткой ссылки (hardlink), чтобы отличить его от ссылок символических - файлов особого типа, о которых речь пойдет в следующем подпараграфе. Количество жестких ссылок и есть значение поля счетчика в inode файла. Очевидно, что возможный его минимум для большинства типов файлов - единица.

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

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

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

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

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


А поскольку содержание файла оторвано от его имени, восстановление случайно удаленного файла по фрагменту имени (на чем основаны DOS-утилиты типа UNERASE и UNDELETE) оказывается невозможным.

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

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

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

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

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


Классификация файлов


Старый пользователь DOS/Windows, не погрязший окончательно в метафоре файлов-документов, возникшей в MacOS (где, надо отдать ей должное, метафора эта вполне оправдана) и внедренной в Windows, начиная с версии 95, помнит о существовании типов файлов, отличающихся своими расширениями, - исполняемых (*.exe, *.com), пакетных (*.bat), текстовых (*.txt), изображений в различных форматах (*.tiff там, или *.gif), и так далее. И потому будет весьма удивлен, когда узнает, что в Unix'ах все они не типизированы никак. И действительно - как ОС может отличить их друг от друга, если имени файла (и, следовательно, расширения) в атрибутах его нет и в помине? В этом смысле говорят, что в Unix нет понятия типизации файла.

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

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

каталоги;

символические ссылки;

специальные файлы устройств;

именованные каналы и сокеты;

обычные, или регулярные, файлы.

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



Команда chmod и umask


Как и следует из ее имени, команда chmod предназначена для смены атрибутов доступа - чтения, изменения и исполнения. В отношении единичного файла делается это просто:

$ chmod [атрибуты] filename

Атрибуты доступа могу устанавливаться с использование как символьной, так и цифровой нотации. Первый способ - указание, для каких атрибутов принадлежности (хозяина, группы и всех остальных) какие атрибуты доступа задействованы. Атрибуты принадлежности обозначаются символами u (от user) для хозяина файла, g (от group) - для группы, o (от other) для прочих и a (от all) - для всех категорий принадлежности вообще. Атрибуты доступа символизируются литерами r (от read), дающей право чтения, w (от write) - право изменения и x (от execute) - право исполнения.

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

Для пояснения сказанного приведу несколько примеров. Так, команда

$ chmod u+w filename

установит для хозяина (u) право изменения (+w) файла filename, а команда

$ chmod a-x filename

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

$ chmod +x filename

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

С помощью команды

$ chmod go=rx filename

можно присвоить группе принадлежности файла filename и всем прочим (не хозяину и не группе) право на его чтение и исполнение с одновременным отнятием права изменения.

Наконец, команда chmod в состоянии установить и дополнительные атрибуты режима для файлов, такие, как биты SUID и GUID, или, скажем, атрибут sticky.
Так, в некоторых системах (например, во FreeBSD - в Linux-дистрибутивах я с таким не встречался) XFree86 подчас по умолчанию устанавливается без атрибута суидности на исполнимом файле X-сервера. Это влечет за собой невозможность запуска Иксов от лица обычного пользователя. Один из способов борьбы с этим - просто присвоить этому файлу бит суидности:

$ chmod u+s /usr/X11R6/bin/XFree86

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

Приведенные примеры можно многократно умножить, но, думается, их достаточно для понимания принципов работы команды chmod с символьной нотацией атрибутов.

Цифровая нотация - еще проще. При ней достаточно указать сумму присваиваемых атрибутов в восьмеричном исчислении (4 - атрибут чтения, 2 - атрибут изменения и 1 - атрибут исполнения; 0 символизирует отсутствие любых атрибутов доступа) для хозяина (первая позиция), группы (вторая позиция) и прочих (третья позиция). Все атрибуты доступа, оставшиеся вне этой суммы, автоматически отнимаются у данного файла. То есть команда

$ chmod 000 filename

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

$ chmod =rwx filename

в символьной нотации. А команда

$ chmod 777 filename

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

$ chmod 4711 /usr/X11R6/bin/XFree86

Как и для команд chown и chgrp, наиболее значимые опции команды chmod - это --reference и -R. И смысл их тот же самый. Первая устанавливает для файла (файлов) атрибуты доступа, идентичные таковым референсного файла, вторая - распространяет действие команды на все вложенные подкаталоги и входящие в них файлы.



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

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

$ umask 22

Вывод прав дается в символьной нотации, нули (то есть отсутствие "отъема" прав у кого-либо) игнорируется. Если же в качестве аргумента указать "отнимаемые" права - все вновь создаваемые файлы будут иметь новые атрибуты доступа. Например, команда

$ umask 000

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

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


Команда ln


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

$ ln filename linkname

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

Символическая ссылка создается той же командой ln, и с теми же аргументами, но со специальной опцией -s:

$ ln -s filename linkname

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

В некоторых системах, говорят, допустимо и создание жестких ссылок на каталог (с помощью опций --directory, -d или -F), однако - исключительно от лица суперпользователя. К BSD и Linux это не относится.

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

$ ln -s linkname linkname2

создаст символическую ссылку linkname2, ссылающуюся на ссылку же linkname, и только последняя будет указывать на обычный файл или каталог filename. Однако если дать команду

$ ln -n linkname linkname2

то новообразованная ссылка linkname2 будет указывать не на ссылку linkname, а на исходный для последней файл.

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

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

Есть и другие способы сохранить исходный файл при совпадении его имени с именем создаваемой ссылки. Так, опция -b (--backup), прежде чем переписать замещаемый файл, создаст его резервную копию - файл вида filename~. А опция -S (--suffix) не только создаст такую копию, но и припишет к ее имени какой-либо осмысленный суффикс. Так, в результате команды

$ ln -sf --S=.old filename1 filename2

существовавший ранее регулярный файл filename2 будет замещен символической ссылкой на файл filename1, однако содержимое оригинала будет сохранено в файле filename2.old. Опция же -V METHOD (--version-control=METHOD) позволяет последовательно нумеровать такие копии замещаемых симлинками файлов. Значения METHOD могут быть:

t, numbered - всегда создавать нумерованную копию;

nil, existing - создавать нумерованную копию только в том случае, если хоть одна таковая уже существует, если же нет - создавать обычную копию;

never, simple - всегда создавать обычную копию;

none, off - не создавать копию замещаемого файла вообще.

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


Команда mkdir


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

Команда mkdir требует обязательного аргумента - имени создаваемого каталога. Аргументов может быть больше одного - в этом случае будет создано два или больше поименованных каталогов. По умолчанию они создаются как подкаталоги каталога текущего. Можно создать также подкаталог в существующем подкаталоге:

$ mkdir parentdir/newdir

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

$ mkdir ../dirname1/dirname2

или в форме абсолютной:

$ mkdir /home/username/dirname1/dirname2

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

$ mkdir ../parentdir/{dirname1,dirname2,...,dirname#}

Такой прием позволяет одной командой создать дерево каталогов проекта. Например, скелет web-сайта, который потом можно наполнить пустыми файлами с помощью команды touch.

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

$ mkdir --mode=### dirname

или

$ mkdir -m ### dirname

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

$ mkdir -m a+rwx dirname

создаст каталог с теми же атрибутами полного доступа для всех.

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

$ mkdir -p dirlevel1/dirlevel2/dirlevel3

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

$ mkdir -p dirlevel1/dirlevel2/{dirlevel31,...,dirlevel3#}



Команда mkfifo


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

$ mkfifo [options] filename

А опция здесь - всего одна -m (--mode), и, как нетрудно догадаться по аналогии с командой mknod, устанавливает она атрибуты доступа.



Команда mknod


Команда mknod предназначается для создания файлов специального типа - файлов устройств. В качестве аргументов она требует:

имени устройства в форме /dev/dev_name;

указания его типа - символьного - c (например, виртуальные терминалы) или блочного - b (например, дисковые накопители и их разделы);

старшего номера (major) - уникального идентификатора, характеризующего родовую группу устройств (например, 4 - идентификатор виртуальных терминалов);

младшего номера (minor), являющегося идентификатором конкретного устройства в своей группе (например, младший номер 0 в группе старшего номера 4 - идентификатор первой, системной, виртуальной консоли, а 63 - идентификатор последней теоретически возможной из них).

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

$ mknod --mode=200 /dev/tty63 c 4 63

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

На практике команда mknod часто используется в опосредованном виде - в составе сценария /dev/MAKEDEV, автоматизирующего процесс создания файлов устройств. В частности, он делает ненужным знание старшего и младшего номеров устройств. Правда, только для тех из них, которые были предусмотрены при разработке сценария. Если же требуемое устройство не входит в число охваченных данным вариантом /dev/MAKEDEV, использования команды mknod в явном виде не избежать. Правда, все более широкое внедрение файловой системы devfs или (пока только в Linux) механизма udev, позволяющих создавать файлы устройств при загрузке (причем - только реально существующих в системе) или "на лету", при "горячем" подключении, скоро сделают команду mknod анахронизмом.



Команда touch для атрибуции


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

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

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

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

$ touch exist_file

она присвоит всем его временным атрибутам (atime, ctime, mtime) значения текущего момента времени. Изменение временных атрибутов можно варьировать с помощью опций. Так, если указать только одну из опций -a, -c, или -m, то текущее значение времени будет присвоено только атрибуту atime, ctime или mtime, соответственно. Если при этом использовать еще и опцию -d [значение], то любому из указанных атрибутов (или им всем) можно присвоить любую временную метку, в том числе и из далекого будущего. А посредством опции -r filename файл-аргумент получит временные атрибуты, идентичные таковым референсного файла filename.



Команды chown и chgrp


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

Формат команды chown - следующий:

$ chown newowner filename

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

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

$ chgrp newgroup filename

от его лица припишет файл filename к группе newgroup. Однако и здесь есть ограничение - результат будет достигнут, только если хозяин файла является членом группы newgroup, иначе опять придется прибегнуть к полномочиям администратора.

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

$ chown newowner:newgroup filename

Или так:

$ chown newowner.newgroup filename

Где, понятное дело, под именем newowner выступает новый хозяин файла, а под именем newgroup - новая группа, к которой он приписан.

В обеих командах вместо имени хозяина и группы могут фигурировать их численные идентификаторы (UID и GID, соответственно). Это имеет смысл, например, при совместном использовании файлов в разных операционных системах. Так, даже единственный пользователь имя_рек в каком-либо варианте Linux и в BSD по умолчанию имеет разные идентификаторы, и чтобы сделать его владельцем неких файлов и там, и там, именно численный идентификатор должен фигурировать в качестве параметра команды chown.

Для команд chown и chgrp поддерживается один и тот же набор опций. Наиболее интересны (и важны) две из них. Опция --reference позволяет определить хозяина файла и его принадлежность к группе не явным образом, а по образу и подобию файла, имя которого выступает в качестве значения опции. Так, команда

$ chown --reference=ref_filename filename

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

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

$ chgrp -R newgroup ~/*

А суперпользователь тем же способом может установить единообразные атрибуты принадлежности "по образцу" для всех компонентов любого каталога:

$ chown -R --reference=ref_filename \ /somepath/somecat/*



Команды touch, cat, tee


Первая из указанных команд в форме

$ touch filename

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

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

$ touch index.html about.html content.html [...]

Можно, воспользовавшись приемом группировки аргументов (об этом говорится в ), и заполнить файлами все подкаталоги текущего каталога:

$ touch dirname1/{filename1,filename2} \ dirname2/{filename3,filename4}

и так далее. Правда, сама команда touch создавать подкаталоги не способна - это следует сделать предварительно командой mkdir (о которой - абзацем ниже).

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

$ cat > filename

создать новую строку (нажатие клавиши Enter) и ввести символ конца файла (комбинацией клавиш Control+Z). Разумеется, предварительно в этот файл можно и ввести какой-нибудь текст, однако это уже относится к управлению контентом, о чем речь будет в .

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

$ ls dir | tee filename

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



Манипулирование файлами


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

$ cp file_source file_target

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

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

$ cp file_source dir/subdir/file_target

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

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

$ cp file1 file2 ... file3 dir/

В этом случае в целевом каталоге dir/ будут созданы новые файлы, идентичные по содержанию файлам file1, file2 и т.д.

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

$ cp -i file1 file2 overwrite file2? (y/n [n])

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

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

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

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

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

Кроме простого копирования файлов, существует команда для копирования с преобразованием - dd. Обобщенный ее формат весьма прост:



$ dd [options]

то есть она просто копирует файл стандартного ввода в файл стандартного вывода, а опции описывают условия преобразования входного потока данных в выходной. Реально основными опциями являются if=file1, подменяющая стандартный ввод указанным файлов, и of=file2, проделывающая ту же операцию со стандартным выводом.

А далее - прочие условия преобразования, весьма обильные. Большинство из них принимают численные значения в блоках:

опции ibs=n и obs=n устанавливают размер блока для входного и выходного потоков, bs=n - для обоих сразу;

опция skip=n указывает, сколько блоков нужно пропустить перед записью входного потока;

опция count=n предписывает скопировать из входного потока лишь указанное количество блоков, отсчитываемых с начала файла-источника.

Имеется и опция conv=value, которая преобразует входной поток в соответствие с принятыми значениями, например, из формата ASCII в формат EBCDIC, рекомендуемый для использования в ОС на базе Unix System V.

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

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



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

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

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

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

Аналогичный смысл имеет и удаление файлов, выполняемое командой

$ rm filename

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


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

Командой rm файлы-аргументы будут удалены в общем случае без предупреждения. Подобно командам cp и mv, для команды rm предусмотрены опции -i (запрос на подтверждение) и -f (принудительное удаление вне зависимости от настроек оболочки).

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

$ rm -file

в ответ последует сообщение об ошибке типа

rm: illegal option -- l

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

$ rm -- -file

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

Это делает использование опции -R весьма опасным: возможно, набивший оскомину пример

$ rm -R /

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

Специально для удаления каталогов предназначена команда

$ rmdir

которая способна удалить только пустой каталог. Кроме того, с опцией -p она может сделать это и в отношении каталогов родительских - но также только в том случае, если они не содержат файлов.


Навигация по файловой системе


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

$ pwd

последует

/home/username

Команда pwd имеет всего две опции: -L и -P. Первая выводит т.н. логический путь к текущему каталогу. То есть, таковым является, скажем, каталог /usr/src/linux, являющий собой символическую ссылку на каталог /usr/src/linux-номер_версии, то в ответ на

$ pwd -L

так и будет выведено

/usr/src/linux

Впрочем, тот же ответ последует и на команду pwd без опций вообще. Если же дать эту команду в форме

$ pwd -P

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

/usr/src/linux-2.4.19-gentoo-r9

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

$ cd pathname

где pathname - путь к искомому каталогу в абсолютной (относительно корня) или относительной (относительно текущего каталога) форме.

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

$ which tcsh zsh bash /bin/tcsh /bin/zsh /bin/bash

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


Более широкие возможности поиска - у команды whereis. По умолчанию, без опций, она для заданного в качестве аргумента имени выводит список бинарных файлов, man-страниц и каталогов с исходными текстами:

$ whereis zsh zsh: /bin/zsh /etc/zsh /usr/lib/zsh /usr/share/zsh \ /usr/man/man1/zsh.1.gz /usr/share/man/man1/zsh.1.gz

Соответствующими опциями можно задать поиск файлов одного из этих типов: -b - бинарных, -m - страниц руководств, -s - каталогов с исходниками. Дополнительные опции -B, -M, -S (в сочетании с опцией -f) позволяют определить исходные каталоги для их поиска.

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

$ locate zsh

будет выведен список вроде следующего:

/bin/zsh /bin/zsh-4.0.6 /etc/zsh /etc/zsh/zlogin /etc/zsh/zshenv /etc/zsh/zshrc

и так далее. Команда locate при этом обращается к базе данных, расположенной в каталоге вроде /var/spool/locate/locatedb или /var/db/locate.database (точный имя и путь в разных системах могут варьировать). По умолчанию эта база данных пуста - и перед использованием команды locate должна быть наполнена содержанием. Для этого предназначен сценарий /usr/bin/updatedb или, в иных системах, /usr/libexec/locate.updatedb (обращаем внимание на полный путь во втором случае - поскольку каталоги типа /usr/libexec/ в переменной PATH обычно не указывается, именно таким способом и следует его запускать). Сценарий этот извлекает сведения из базы данных установленных пакетов - например, в BSD-системах, /var/db/pkg. При активной доустановке программ база данных для команды locate нуждается в периодическом обновлении (за обновление базы данных установленных пакетов обычно отвечает система пакетного менеджмента данного дистрибутива или ОС).

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


О времени и о файле


В заключение - несколько слов о временных атрибутах файла, которые, как станет ясным из главы про управление пакетами, в некоторых случаях оказываются очень важными. Их - опять же три: время доступа (atime - от Access Time), время модификации (mtime - от Modification Time) и время изменения (ctime - от Change Time). Первый атрибут фиксирует время последнего обращения к файлу - любого, например, просмотра его командой less. Атрибут этот на практике используется редко, а пересчет времени доступа для изобилия файлов на древе Unix-системы отъедает ресурсы. И потому часто он (пересчет, конечно, а не атрибут) отменяется, о чем будет разговор в и монтировании файловых систем.

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

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




Обычные файлы


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

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

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

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



Получение информации о файлах


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

$ ls [options] names

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

Начать с того, что команда ls, данная без всяких опций, по умолчанию выводит только имена файлов, причем опуская т.н. dot-файлы, имена которых начинаются с точки (это - некие аналоги скрытых файлов в MS DOS и Windows). Кроме того, если в качестве аргумента указано имя каталога (или аргумент не указан вообще, что подразумевает текущий каталог), из списка имен его файлов не выводятся текущий (.) и родительский (..) каталог.

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

Кроме имени, любой файл идентифицируется своим номером inode. Для его вывода используется опция -i:

$ ls -i 12144 content.html

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

$ ls -R unixforall: about/ apps/ diffimages/ distro/ signature.html sys/ anons/ content/ difftext/ gentoo/ statistics/ u4articles/

unixforall/about: about_lol.html about_lol.txt index.html

unixforall/anons: anons_dc.html

Опция же -d, напротив, запрещает вывод содержимого вложенных подкаталогов.

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

$ ls -F dir1/ dir2/ dir3@ file1 file2* file3@


Другое средство для визуального различия типов файлов - колоризация, для чего применяется опция -G. Цвета шрифта, воспроизводящего имена, по умолчанию - синий для каталогов, лиловый (magenta) для символических ссылок, красный - исполнимых файлов, и так далее. Для файлов устройств, исполнимых файлов с атрибутом "суидности", каталогов, имеющих атрибут sticky, дополнительно колоризуется и фон, на котором выводится шрифта, воспроизводящий их имена. Подробности можно посмотреть в секции ENVIRONMENT man-страницы для команды ls. Впрочем, колоризация работает не на всех типах терминалов (и не во всех командных оболочках).

По умолчанию команда ls выводит список файлов в порядке ASCII-кодов первого символа имени. Однако есть возможность его сортировки в порядке времени модификации (-t), изменения статуса (-c) или времени доступа (-tu), а также в порядке, обратном любому из перечисленных (-r). Кроме того, опция -f отменяет какую-либо сортировку списка вообще.

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

$ ls -s ../book total 822 656 book.html 4 content1.html 86 var_part2.html 24 command.html 38 part2.html 6 command.txt 8 shell_tmp.html

Добавление к опции -s еще и опции -k (то есть ls -sk) выведет всю ту же информацию в килобайтах.

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

$ ls -1 dir1 dir2 dir3 file1 file2 file3

До сих пор речь шла о кратком формате вывода команды ls. Однако более информативным является т.н. длинный ее формат, вывод в котором достигается опцией -l и автоматически влечет за собой одноколоночное представление списка:

$ ls -l total 8 drwxr-xr-x 2 alv alv 512 8 май 18:04 dir1 drwxr-xr-x 3 alv alv 512 8 май 17:43 dir2 lrwxr-xr-x 1 alv alv 4 9 май 07:59 dir3 -> dir2 -rw-r--r-- 1 alv alv 14 8 май 10:39 file1 -rwxr-xr-x 1 alv alv 30 9 май 08:02 file2 lrwxr-xr-x 1 alv alv 2 8 май 10:57 file3 -> f1



Можно видеть, что по умолчанию в длинном формате выводятся:

сведения о типе файла (- - регулярный файл, d - каталог, l - символическая ссылка, c - файл символьного устройства, b - файл блочного устройства) и атрибуты доступа для различных атрибутов принадлежности (о чем было сказано достаточно);

количество жестких ссылок на данный идентификатор inode;

имя пользователя - владельца файла, и группы пользователей, которой файл принадлежит;

размер файла в блоках;

время модификации файла с точностью до месяца, дня, часа и минуты (в формате, принятом в данной locale);

имя файла и (для символических ссылок) имя файла-источника.

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

$ ls -linT total 8 694402 drwxr-xr-x 2 1000 1000 512 8 май 18:04:56 2002 dir1 694404 drwxr-xr-x 3 1000 1000 512 8 май 17:43:31 2002 dir2 673058 lrwxr-xr-x 1 1000 1000 4 9 май 07:59:08 2002 dir3 -> dir2 673099 -rw-r--r-- 1 1000 1000 14 8 май 10:39:38 2002 file1 673059 -rwxr-xr-x 1 1000 1000 30 9 май 08:02:23 2002 file2 673057 lrwxr-xr-x 1 1000 1000 2 8 май 10:57:07 2002 file3 -> f1

Разумеется, никто не запрещает использовать в длинном формате и опции визуализации (-F и -G), и опции сортировки (-r, t, tu), и любые другие, за исключением опции -C - указание ее ведет к принудительному выводу списка в многоколоночной форме, что естественным образом подавляет длинный формат представления.

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



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

исполняемые бинарные файлы с указанием их формата (например, ELF), архитектуры процессора, для которых они скомпилированы, характер связи с разделяемыми библиотеками (статический или динамический)

исполняемые сценарии с указанием оболочки, для которой они созданы;

текстовые и html-документы, часто с указанием используемого набора символов.

Последнему, впрочем, для русскоязычных документов доверять особо не следует: кодировка KOI8-R в них вполне может быть обозвана ISO-8859.

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

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

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


Право на файл


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

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

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

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

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

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

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

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



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

Кроме атрибутов принадлежности и доступа, которые присваиваются файлу (или, напротив, отнимаются у него) в обязательном порядке, он может иметь и три дополнительных атрибута, называемых иногда атрибутами режима. Для названия их удобочитаемого русского перевода не существует. А на вражьей мове это атрибут SUID (Set User IDentificator), иногда именуемый битом "суидности" (не путать с суицидом), SGID (Set Group IDentificator) и sticky (что можно трактовать как атрибут "липкости" или "сохранности"). Первые два имеют смысл только для исполняемых обычных файлов, последний - в основном для каталогов.

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

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


Установка его целесообразна для каталогов, хранящих всякого рода временные данные (типа /tmp и некоторых подкаталогов ветви /var), права доступа к которым по умолчанию (и по смыслу) допускают их модификацию всеми пользователями. Однако, если право на запись временных файлов юзер вряд ли сможет использовать во вред, то удаление из таких каталогов чужих файлов вряд ли будет приветствоваться их хозяином. И именно для предотвращения такой ситуации предназначен атрибут sticky.

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

В обычном выводе команды ls -l имена пользователя и группы находятся в третьем и четвертом полях, а атрибуты доступа объединены в первом. Он имеет вид

-rwxrwxrwx

что расшифровывается следующим образом. Первая позиция последовательности - тип файла (символ дефиса, -, в примере означает, что мы имеем дело с обычным файлом, для каталога там был бы символ d - от directory, для символьного устройства - символ c, для блочного - символ b). Следующие три символа определяют атрибуты доступа для хозяина файла: r - чтение, w - изменение, x - исполнение. Две последние тройки символов - суть то же самое, но для группы и прочих, соответственно. То есть в данном примере фигурирует исполняемый файл, открытый на чтение, запись и запуск для всех. Если же у кого-либо какое-то право отнято - в соответствующей позиции мы увидим символ дефиса. Так, атрибуты нового текстового файла по описанной выше умолчальной схеме будут выглядеть как

-rw-r--r--

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



ls -l /usr/bin/passwd

будет выглядеть так:

-rwsr-xr-x

а прочие варианты предлагается домыслить самостоятельно.

Выше при описании прав доступа использована т.н. символьная нотация, простая и мнемонически понятная (r - от read, o - от other, и т.д.). Однако наряду с ней существует (и активно используется) нотация цифровая, где права доступа обозначаются числами типа 644. Поначалу она кажется загадочной. Однако при ближайшем рассмотрении - ничего подобного. Первая цифра соответствует правам хозяина файла, вторая - правам членов группы, третья - правам разных там прочих, как и при символьной нотации. А сама цифра представляет собой простую арифметическую сумму прав, также выраженных численно - только в двоичной системе счисления, трансформированной в восьмеричную для компактности. А именно: наличию любого права соответствует двоичная единица, отсутствию - двоичный ноль. То есть символьной форме rwxrwxrwx будет соответствовать двоичная 111 111 111, что в восьмеричном пересчете и даст "число юзверя" - 777 (полный доступ для всех атрибутов принадлежности). Образуется она из суммы прав чтения (восьмеричное 4), изменения (восьмеричное 2) и исполнения (восьмеричная 1) в трех позициях. Из чего можно догадаться, что число 000, напротив, означает отсутствие прав на любые действия у кого бы то ни было.

Арифметические вычисления того, какие числа соответствуют каким соотношениям прав, я предоставляю читателю (слаб стал в устном счете с тех пор, как бросил преферанс). Скажу только, что "умолчальная" атрибутика новорожденного файла rw-r--r-- в численной нотации будет выглядеть как 644.

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

Догадались, почему в первой позиции стандартной совокупности прав фигурирует 6, хотя в аргументе команды umask видим 0? Правильно, потому, что ни один создаваемый непосредственно пользователем файл не рождается как исполняемый: этот атрибут должен быть присвоен ему принудительно.А от рождения право на исполнение обретают только двоичные файлы, создаваемые компилятором (например, gcc).


Резервное копирование


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

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

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

$ dd if=/dev/ad0s1a of=/dev/ar0s1a

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

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

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


$ cpdup / /mnt $ cpdup /var /mnt/var $ cpdup /etc /mnt/etc $ cpdup /dev /mnt/dev $ cpdup /usr /mnt/usr

Здесь каталоги /, /var и так далее - точки монтирования корня и отдельных его ветвей файловой системы установочного LiveCD, а /mnt, /mnt/var - заблаговременно созданные, отформатированные и смонтированные разделы на винчестере, на который инсталлируется DragonFlyBSD.

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

Мне известно два инструмента для записи CD-дисков: кросс-платформенный комплект cdrtools и утилита burncd, используемая только в BSD-системах. Первый к настоящему времени не описал только ленивый. А вот утилита burncd известна относительно мало, и по причинам, которые станут ясными несколько позже, заслуживает подробного рассмотрения.

Подобно большинству исконных BSD-инструментов, использование burncd для записи дисков - дело не простое, а очень простое. И требует оно только наличия записывающего привода с ATAPI-интерфейсом. При этом нет необходимости ни в каких специфических настройках типа включения эмуляции SCSI через IDE (без чего до недавнего времени было не обойтись в Linux при использовании cdrtools).

Правда, обычно запись CD-диска начинается с создания его образа. Для чего требуется программа mkisofs из все того же пакета cdrtools. Хотя во FreeBSD и DragonFlyBSD она доступна в качестве самостоятельного порта или автономного бинарника, не требующего установки прочих компонентов оригинального пакета. Собственно создание образа происходит так:

$ mkisofs -R -J -o name.iso path2data

Здесь опция -R обеспечивает поддержку расширения стандарта ISO9660 - Rock Ridge для Unix-систем (длинные имена, множественные точки в именах файлов, сохранение атрибутов доступа и принадлежности файлов и каталогов).


Опция -J - это поддержка расширения Jouliet для систем семейства Windows (то есть длинные имена файлов будут видны и там). Опция -o имеет своим значением имя файла создаваемого iso-образа. Ну а path2data - путь к каталогу, из содержимого которого будет создаваться образ.

Непосредственно запись диска выполняется BSD-утилитой burncd. Например, это можно сделать такой командой:

$ burncd -e -s max -f /dev/acd0 data iso_name fixate

Значения опций - следующие:

-e обеспечивает выдвижение лотка после записи,

-s - скорость записи (по умолчанию - 4, значение max - обеспечивает максимально возможную для данного накопителя и "болванки"),

-f - имя файла устройства (в примере - /dev/acd0).

Команда fixate указывает на фиксирование сессии (подразумевается односессионная запись). Ну а data предписывает запись диска с данными (а не аудиоCD) с образа name.iso.

У burncd есть еще несколько опций, с которыми можно ознакомиться посредством

$ man 8 burncd

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

Для стирания CD-RW в burncd предусмотрены команды blank (быстрая очистка оглавления диска) и erase (полная очистка диска): $ burncd -e -f /dev/acd0 blank

или $ burncd -e -f /dev/acd0 erase

соответственно. Нужно только помнить, что вторая операция может занять много времени - столько же, сколько и запись).

Если для целей чисто резервного копирования (например, архива вида *.tar.gz) не требуется запись дисков, доступных из других операционок, burncd можно использовать и без предварительного создания iso-образа (и, соответственно, без пакета mkisofs). Все, что для этого нужно (помимо заблаговременно созданного архива подходящего размера) - директива примерно такого вида:

$ burncd -f /dev/acd1c -s max data archive.tar.gz fixate

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



$ tar xzvf /dev/acd1c

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

$ split --split --bytes=650m archive.tar.gz [PREFIX]

где в качестве префикса можно указать какое-либо мнемонически полезное значение (дату создания архива, например), после чего последовательно записать кучу образовавшихся файлов (имеющих вид [PREFIX]aa, [PREFIX]ab, и так далее) почти так же, как было сказано выше:

$ burncd -f /dev/acd1c -s max data [PREFIX]?? fixate

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

$ cp /dev/acd1c path2/file#

Затем они сливаются утилитой cat в единый архив:

$ cat file1 ... file# > archive.tar.gz

который и разворачивается обычным образом.

Возможность применения burncd для резервного копирования без предварительного создания iso-образов определяет, по моему мнению, ее предпочтительность перед cdrecord. Однако на ее использование накладывается несколько ограничений. Во-первых, как я уже сказал, она существует только в BSD-системах - и это, конечно, главное. Во-вторых, она работает только с ATAPI-приводами. И если вероятностью наличия в пользовательской машине CD-R/RW со SCSI-интерфейсом можно пренебречь, то записывающие USB-устройства получают все большее распространение - а перед POSIX-системой последние предстанут в виде SCSI CD ROM. И наконец, с некоторыми моделями даже ATAPI CD ROM burncd отказывается работать категорически (правда, мне встречался один-единственный такой привод, и то уже давно).

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

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


Что при наличии обычного ATAPI CD-R/RW требовало некоторых ухищрений - включения эмуляции SCSI через IDE в Linux или использования модуля ATAPI/CAM в BSD. Разумеется, это не возбраняется и поныне, однако проще воспользоваться возможностью последних версий cdrecord общаться с ATAPI-приводами напрямую, что и будет предметом последующего рассказа (тем более что, повторяю, работа с cdrecord при эмуляции SCSI через IDE была многократно описана ранее).

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

Запись дисков посредством cdrecord, в отличие от burncd, требует обязательного создания iso-образа. Благо, соответствующая утилита - mkisofs, - входит в состав пакета, а сам процесс выполняется точно так же, как было описано выше.

Далее, cdrecord сохранила некоторые реликты своего SCSI-происхождения. И ATAPI-устройство, на которое она записывает, все равно должно иметь номер SCSI-облика (вида dev=#,#,#). Он определяется командой

$ cdrecord -scanbus dev=ATAPI:

вывод которой должен выглядеть примерно так:

Cdrecord 2.0 (i686-pc-linux-gnu) Copyright (C) 1995-2002 JЖrg Schilling scsidev: 'ATAPI:' devname: 'ATAPI' scsibus: -1 target: -1 lun: -1 Warning: Using ATA Packet interface. Warning: The related libscg interface code is in pre alpha. Warning: There may be fatal problems. Using libscg version 'schily-0.7' scsibus0: 0,0,0 0) 'TEAC ' 'CD-W540E ' '1.0C' Removable CD-ROM 0,1,0 1) * 0,2,0 2) * 0,3,0 3) * 0,4,0 4) * 0,5,0 5) * 0,6,0 6) * 0,7,0 7) *

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


Но пакет cdrtools вообще не в ладах с достижениями мысли товарища Аристотеля, поэтому просто запоминаем их.

Дальше все просто. Легко догадаться, что для записи диска программа cdrecord потребует минимум двух аргументов: имени устройства и имени файла iso-образа:

$ cdrecord dev=ATAPI:0,0,0 name.iso

Как обычно, в дополнительных опциях нас никто не ограничивает. В частности, можно задать опцию -speed=#, где в качестве значения # выступит желаемая скорость записи. Но можно этого и не делать: без явного указания скорости cdrecord будет писать максимально быстро - насколько это возможно для данных привода и "болванки". Так что интересней будет дать опцию -v (от обычного verbose), которая обеспечит нас подробной информацией о ходе записи (в том числе о таких параметрах, как максимальная, минимальная и средняя скорость записи, процент заполнения буфера, и т.д.).

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

$ cdrecord -v -eject [-speed=12] dev=ATAPI:0,0,0 name.iso

Отступление по поводу опции speed (или ее отсутствия). Как-то мне пришлось иметь дело с пачкой технологических болванок производства всемирно известной фирмы noname, теоретическая скорость записи на которые на упаковке была обозначена как x12. И поначалу я честно указывал эту цифру в качестве значения опции speed. А потом мне это надоело, и я решил проверить, что же будет без нее. Оказалось, что соседние болванки из пачки отличаются по своим характеристикам на пол-порядка: они с равной вероятностью записывались как на 4-х так и на 24-х скоростях (причем и в последнем случае - без ошибок!).

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


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

Раз уж речь зашла о перезаписи, не лишне вспомнить, как к ней подготовиться (то есть - ослобонить болванку от записанного ранее). Делается это той же командой cdrecord, но с опцией blank=значение, каковое может быть fast (быстрая очистка, при которой стирается оглавление без физического уничтожения данных) или all (полная очистка диска, которая занимает ровно столько же времени, сколько и его запись на всю катушку).

До сих пор речь шла о записи диска "в один присест". Однако никто этого делать не заставляет - мы ведь знаем, что бывает т.н. мультисессионая запись - это если данных на полный CD сразу не накопилось. В этом случае создаем образ диска, как описано выше (объемом хоть в 10 Мбайт - только помним, что на каждую сессию, вне зависимости от количества записанной информации, этого самого объема некоторое количество теряется), и записываем точно также, добавив лишь опцию -multi:

$ cdrecord -v -eject -multi [-speed=#] dev=ATAPI:0,0,0 name1.iso

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

$ cdrecord -msinfo dev=ATAPI:0,0,0

которая выдаст нам последний использованный в прошлой сессии трэк - некое число ###, которое и подставим в командную строку в качестве значения опции -C.

Далее, команде mkisofs требуется сообщить еще и имя устройства, на котором этот трэк был записан. Для чего предназначена опция -M. А вот значением ее будет (внимание!) не трехзначный номер устройства, полученный при выводе команды cdrecord -scanbus, как можно было бы ожидать, а просто имя файла устройства (где ты, дяденька Аристотель?) - типа /dev/cdroms/cdrom (или как он точно обозначается в данной системе).



В итоге команда mkisofs для создания образа второй и последующих сессий приобретет вид:

$ mkisofs -R -J -C ### -M /dev/cdroms/cdrom -o name2.iso /path2data

А вот записывается этот образ самым обычным образом:

$ cdrecord -v -eject [-multi] [-speed=#] dev=ATAPI:0,0,0 name2.iso

Причем, насколько я понял, даже опция -multi в этом случае не обязательна.

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

$ mount -o loop name.iso /mnt_point

После чего содержимое каталога /mnt_point просматривается обычным образом.

Как я говорил, программа cdrecord не позволяет обойтись без создания iso-образа. Однако, если нет намерения оный проверять (ведь POSIX'ивист, как и сапер, не ошибается), никто не обязывает его записывать на диск: те, кто числит себя среди "инженерных Её Величества войск, с содержаньем и в чине сапера", могут перенаправить вывод команды mkisofs по конвейеру прямо на ввод cdrecord:

$ mkisofs -R -J /path2data | cdrecord dev=ATAPI:0,1,0 -

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


Символические ссылки


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

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

Для разрешения этих проблем был придуман особый тип файлов - символические ссылки (symlinks), часто называемые просто ссылками (links). Будем так поступать и мы. А для отличия от них жестких ссылок последние будут именоваться полность. Вообще-то, в русском языке для однозначности за жесткими ссылками хорошо бы закрепить термин "связь", но он почему-то не прижился.

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


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

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

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

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

А что делать, если версия библиотеки - более новая? Ведь большинство разработчиков программ для POSIX-систем обычно свято придерживаются принципа обратной совместимости.


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

Конечно, теоретически правильно написанная программа должна требовать версии не ниже некоторой определенной, однако на практике такая ситуация встречается сплошь и рядом. И разрешается она часто достаточно легко - посредством механизма тех же символических ссылок. То есть обычно достаточно создать симлинк библиотеки имя_рек.5 на имя_рек.6, чтобы обмануть наше приложение и заставить его работать правильно.

Сказанным не исчерпывается сфера применения символических ссылок. В частности, они часто требуются для корректного доступа рядя программ к определенным файлам устройств. Если внимательно просмотреть каталог /dev любой POSIX-системы, там почти наверняка обнаружатся такие файлы, как cdrom, mouse, modem, audio и еще несколько им подобных. Так вот, это обычно не более, чем символические ссылки на файлы реальных устройств, созданные для единообразия доступа к ним, позволяющие абстрагироваться от конкретного конкретного его интерфейса (положения на IDE-контроллере, номера последовательного порта, и так далее), Впрочем, это уже тема следующего раздела, имя которому -


Создание


Работа с файлами начинается с их создания. Конечно, в большинстве случаев файлы (вместе с их контентом) создаются соответствующими приложениями (текстовыми редакторами, word-процессорами и т.д.). Однако есть несколько команд, специально предназначенных для создания файлов. Это - touch, mkdir, ln, mknod, mkfifo. Кроме того, с этой же целью могут быть использованы команды cat и tee.



Венец универсализма: утилита find


В этой заметке речь пойдет о пакете, известном в проекте GNU как findutils. И в первую голову - о команде find (как, впрочем, и о тесно связанной с ней команде xargs). Столь высокая честь выпадает им потому, что посредством этих двух команд можно выполнить если не все, то большинство (Buono Parte) задач, возникающих при работе с файлами.

Кроме find и xargs, в состав набора findutils входят команды locate и updatedb, о которых говорилось ранее, а также команды bigram, code, frcode, реального применения которым я, честно говоря, не знаю (и, соответственно, говорить о них не буду). В BSD-системах те же команды входят в базовый комплект.

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

$ whatis find find(1) - walk a file hierarchy

что применительно случаю можно перевести как "прогулка по файловой системе".

Команда find по своему синтаксису существенно отличается от большинства прочих Unix-команд. В обобщенном виде формат ее можно представить следующим образом:

$ find аргумент [опция_поиска] [значение] \ [опция_действия]

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

$ find / [опция_поиска] [значение] \ [опция_действия]

или домашний каталог пользователя:

$ find ~/ [опция_поиска] [значение] \ [опция_действия]

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


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

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

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

name - поиск по имени файла или по маске имени; в последнем случае метасимволы маски должны обязательно экранироваться (например, - name \*.tar.gz) или заключаться в кавычки (одинарные или двойные, в зависимости от ситуации); этот критерий чувствителен к регистру, но близкий по смыслу критерий iname позволяет производить поиск по имени без различения строчных и заглавных букв;

type - поиск по типу файла; этот критерий принимает следующие значения: f (регулярный файл), d (каталог), s (символическая ссылка), b (файл блочного устройства), c (файл символьного устройства);

user и group - поиск по имени или идентификатору владельца или группы, выступающим в качестве значения критерия; существует также критерии nouser и nogroup - они отыскивают файлы, владельцев и групповой принадлежности не имеющие (то есть тех, учетные записи для которых отсутствую в файлах /etc/passwd и /etc/group); последние два критерия в значениях, разумеется, не нуждаются;

size - поиск по размеру, задаваемому в виде числа в блоках или в байтах - в виде числа с последующим символом c; возможны значения n (равно n блоков), +n (более n блоков), -n (менее n блоков);

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

atime, ctime, mtime - поиск файлов с указанными временными атрибутами; значения временных атрибутов указываются в сутках (точнее, в периодах, кратных 24 часам); возможны формы значений этих атрибутов: n (равно указанному значению n*24 часа), +n (ранее n*24 часа), -n (позднее n*24 часа);



newer - поиск файлов, измененных после файла, указанного в качестве значения критерия (то есть имеющего меньшее значение mtime);

maxdepth и mindepth позволяют конкретизировать глубину поиска во вложенных подкаталогах - меньшую или равную численному значению для первого критерия и большую или равную - для второго;

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

prune - позволяет указать подкаталоги внутри пути поиска, в которых отбора файлов производить не следует.

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

$ find / -fstype ext3 -name zsh*

будет искать файлы, имеющие отношение к оболочке Z-Shell, начиная с корня, но только - в пределах тех разделов, на которых размещена файловая система Ext3fs (на моей машине - это именно чистый корень, за вычетом каталогов /usr, /opt, /var, /tmp и, конечно же, /home.

Критерии отбора файлов могут группироваться практически любым образом. Так, в форме

$ find ~/ -name *.tar.gz newer filename

она выберет в домашнем каталоге пользователя все компрессированные архивы, созданные после файла с именем filename. По умолчанию между критериями отбора предполагается наличие логического оператора AND (логическое "И"). То есть будут отыскиваться файлы, удовлетворяющие и маске имени, и соответствующему атрибуту времени. Если требуется использование оператора OR (логическое "ИЛИ"), он должен быть явно определен в виде дополнительной опции -o между опциями поиска. Так, команда:

$ find ~/ -mtime -2 -o newer filename

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

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


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

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

$ find . -name f* -print ./file1 ./file2 ./dir1/file3

Сходный смысл имеет и опция -ls, однако она выводит более полные сведения о найденных файлах, аналогично команде ls с опциями -dgils:

$ find / -fstype ext3 -name zsh -ls 88161 511 -rwxr-xr-x 1 root root 519320 Ноя 23 15:50 /bin/zsh

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

find: /root: Permission denied

указывающие на каталоги, закрытые для просмотра обычным пользователем, и весьма мешающие восприятию результатов поиска. Чтобы подавить их, следует перенаправить вывод сообщения об ошибках в файл /dev/null, то есть указать им "Дорогу никуда":

$ find / -fstype ext3 -name zsh -ls 2> /dev/null

Идем далее. Опция -delete уничтожит все файлы, отобранные по указанным критериям. Так, командой

$ find ~ -atime +100 -delete

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

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

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


И потому достигнем той же цели следующим образом:

$ find ~/ -atime +100 -exec rm -i {} \;

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

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

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

Приведенный пример, хотя и вполне жизненный, достаточно элементарен. Рассмотрим более сложный случай - собирание в один каталог всех скриншотов в формате PNG, разбросанных по древу домашнего каталога:

$ find ~/ -name *.png -exec cp {} imagesdir \;

В результате все png-файлы будут изысканы и скопированы (или - перемещены, если воспользоваться командой mv вместо cp) в одно место.

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

Зачем и отчего это нужно? Поясню на примере. Как-то раз, обзаведясь огромным по тем временам (40 Гбайт) винчестером, я решил собрать на него все нужные мне данные, рассеянные по дискам CD-R/RW (суммарным объемом с полкубометра) и нескольким сменным винчестерам, одни из которых были отформатированы в FAT16, другие - в FAT32, третьи - вообще в ext2fs (к слову сказать, рабочей моей системой в тот момент была FreeBSD).


Сгрузив все это богачество в один каталог на новом диске, я создал в нем весьма неприглядную картину.

Ну, во-первых, все файлы, скопированные с CD и FAT-дисков, получили (исключительно из-за неаккуратности монтирования, с помощью должных опций этого можно было бы избежать, но - спешка, спешка...) биты исполняемости, хотя были это лишь файлы данных. Казалось бы, мелочь, но иногда очень мешающая; в некоторых случаях это не позволяет, например, просмотреть html-файл в Midnight Commander простым нажатием Enter. Во-вторых, для некоторых каталогов, напротив, исполнение не было предусмотрено ни для кого - то есть я же сам перейти в них не мог. В третьих, каталоги (и файлы) с CD часто не имели атрибута изменения - а они нужны мне были для работы (в т.ч. и редактирования). Конечно, от всех этих артефактов можно было бы избавиться, предусмотрев должные опции монтирования накопителей (каждого накопителя - а их число, повторяю, измерялось уже объемом занимаемого пространства), да я об этом и не подумал - что выросло, то выросло. Так что ситуация явно требовала исправления, однако проделать вручную такую работу над данными более чем в 20 Гбайт виделось немыслимым.

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

$ find ~/dir_data -type f \ -exec chmod a-x,u+w {} \;

Далее - поиск каталогов и обратная процедура над итоговой выборкой:

$ find ~/dir_data -type d \ -exec chmod a+xr,u+w {} \;

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

Так, с помощью команды find легко наладить периодическое архивирование результатов текущей работы.


Для этого перво- наперво создаем командой tar полный архив результатов своей жизнедеятельности:

$ tar cvf alldata.tar ~/*

А затем в меру своей испорченности (или, напротив, аккуратности), время от времени запускаем команду

$ find ~/ -newer alldata.tar \ -exec tar uvf alldata.tar {} \;

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

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

Тем не менее, ситуация вполне разрешима. И сделать это призвана команда xargs. Она определяется как построитель и исполнитель командной строки со стандартного ввода. А поскольку на стандартный ввод может быть направлен вывод команды find - xargs воспримет результаты ее работы как аргументы какой-либо команды, которую, в свою очередь, можно рассматривать как аргумент ее самоё (по умолчанию такой командой-аргументом является /bin/echo).

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

$ sysctl -a | grep kern.argmax

И способы изменить этот лимит мне не известны - был бы благодарен за соответствующую информацию.

Поэтому в реальности у меня не возникало необходимости в команде xargs и, соответственно, я не занимался ее глубоким изучением.Так что заинтересованных отсылаю к соответствующей man-странице.


в себя множество отдельных утилит


Средства управления файлами в Base POSIX включают в себя множество отдельных утилит для создания файлов различных типов, установки и изменения их атрибутов, копирования, перемещения, переименования и удаления файлов, а также получения информации о файлах. Кроме того, к сфере файловых операций следует отнести и средства их архивирования, компрессии, да и вообще резервного копирования. Наконец, к этому же кругу относится утилита find - практически универсальное средство не только для поиска файлов, но и для массовой их обработки.
Ниже я рассмотрю основные команды, предназначенные для файловых операций, вместе с их наиболее используемыми опциями. Эти команды имеются в любом дистрибутиве Linux (где они объединены в несколько отдельных пакетов, таких, как coreutils, findutils и так далее) и в любой BSD-системе, хотя конкретные их реализации могут несколько различаться. Здесь будет говориться главным образом о самостоятельных командах и утилитах - тех, которым соответствует собственный исполняемый файл (обычно в каталогах /bin, /sbin, /usr/bin или /usr/sbin). Случаи применения встроенных команд оболочки (а они подчас совпадают с внешними утилитами по назначению и имени) будут оговариваться специально.
Чтобы не повторяться, напомню, что почти все описанные ниже команды имеют три стандартные опции (т.н. GNU Standard Options): --help (иногда также -h или -?) для получения помощи, --verbose (иногда также -v) для вывода информации о текущей версии, и --, символизирующей окончание перечня опций (т.е. любой символ или их последовательность после нее интерпретируются как аргумент). Так что далее эти опции в описаниях команд фигурировать не будут.