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

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

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

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

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

Содержание

Введение

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

Ниже я рассмотрю основные команды, предназначенные для файловых операций, вместе с их наиболее используемыми опциями. Эти команды имеются в любом дистрибутиве Linux (где они объединены в несколько отдельных пакетов, таких, как coreutils, findutils и так далее) и в любой BSD-системе, хотя конкретные их реализации могут несколько различаться. Здесь будет говориться главным образом о самостоятельных командах и утилитах - тех, которым соответствует собственный исполняемый файл (обычно в каталогах /bin, /sbin, /usr/bin или /usr/sbin). Случаи применения встроенных команд оболочки (а они подчас совпадают с внешними утилитами по назначению и имени) будут оговариваться специально.

Чтобы не повторяться, напомню, что почти все описанные ниже команды имеют три стандартные опции (т.н. GNU Standard Options): --help (иногда также -h или -?) для получения помощи, --verbose (иногда также -v) для вывода информации о текущей версии, и --, символизирующей окончание перечня опций (т.е. любой символ или их последовательность после нее интерпретируются как аргумент). Так что далее эти опции в описаниях команд фигурировать не будут.

Создание

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

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

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

$ touch filename

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

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

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

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

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

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

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

$ cat > filename

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

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

$ ls dir | tee filename

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

Команда 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#}

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

Команда 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 анахронизмом.

Команда mkfifo

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

$ mkfifo [options] filename

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

Атрибуция

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

Команды 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/*

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

Команда 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.

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

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

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

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

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

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

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

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

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

Для пользователя 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.

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

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

Венец универсализма: утилита 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-странице.

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

Алексей Федорчук - Интермедия: управление файлами   Версия для печати