О блоге

Все новые материалы размещаются на Блогосайте alv.me. Старые - в процессе переноса.

05.09.2008

Файловые утилиты: создание и атрибуция

2002-2005 гг

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

  • их созданию;
  • навигации по файловой системе;
  • получению информации о файлах;
  • манипулированию существующими файлами;
  • поиску файлов.

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

Характеристика средств

Средства управления файлами в Base Linux входят в состав пакета coreutils (в него с некоторого времени объединены три ранее самостоятельных комплекта - fileutils, sh-utils и textutils). Он включает в себя множество команд для создания файлов различных типов, установки атрибутов файлов, копирования, перемещения, переименования и удаления файлов, а также получения информации о файлах. Последней цели служит также набор file, включающий единственную, одноименную, команду.

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

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

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

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

Создание

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

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

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

$ touch filename

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

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

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

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

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

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

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

$ cat > filename

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

Интересно создание файлов с помощью команды 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

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

Опция --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), однако - исключительно от лица суперпользователя.

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

$ ln -s linkname linkname2

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

$ ln -n linkname linkname2

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

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

Команда mknod

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

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

Команда mkfifo

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

$ mkfifo [options] filename

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

Атрибуция

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

Для команд 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

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

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

Атрибуты доступа могу устанавливаться с использование как символьной, так и цифровой нотации (подробнее об этом - в POSIX-саге). Первый способ - указание, для каких атрибутов принадлежности (хозяина, группы и всех остальных) какие атрибуты доступа задействованы. Атрибуты принадлежности обозначаются символами 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 (и о них будет разговор в POSIX-саге). Так, в некоторых системах (например, во FreeBSD - в Linux-дистрибутивах я с таким не встречался) XFree86 подчас по умолчанию устанавливается без атрибута суидности на исполнимом файле X-сервера. Это влечет за собой невозможность (без специальных wrapper'ов) запуска Иксов от лица обычного пользователя. Один из способов борьбы - просто присвоить этому файлу бит суидности:

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

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

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