В этом очерке будут рассмотрены утилиты командной строки разного назначения — комплекс так называемых классических UNIX-утилит в их современных свободных реализациях, используемых в дистрибутивах Linux, в том числе и в Mint.

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

Эта рубрика посвящена самой главной команде — man, а также сопутствующим ей материям. Содержание её — не информация о тех или иных командах, или свойствах системы, а метаинформация: информация о том, как получить нужную информацию. То есть выработке некоторых навыков, которые у применителя Linux должны быть доведены до уровня рефлексов.

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

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

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

  1. man1 — команды и прикладные программы пользователя;

  2. man2 — системные вызовы;

  3. man3 — библиотечные функции;

  4. man4 — драйверы устройств;

  5. man5 — форматы файлов;

  6. man6 — игры;

  7. man7 — различные документы, не попадающие в другие группы (в том числе относящиеся к национальной поддержке);

  8. man8 — команды администрирования системы.

Кроме того, имеется несколько подкаталогов с локализованными man-страницами, в том числе и русскоязычными, имеющими ту же структуру, хотя и обычно не полную. Так, подкаталог с русскоязычными страницами, /usr/share/man/ru, включает в себя только группы man1, man5, man7 и man8.

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

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

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

$ man 1 ls

Причём номер группы необходим только в том случае, если одноимённые документы имеются в разных группах. Для пользовательских команд он обычно не нужен, так как все равно просмотр групповых каталогов идёт сначала в man1, затем — в man8, и только потом — во всех остальных (в порядке возрастания номеров). Так что для получения информации, например, по команде ls достаточно ввести следующее:

$ man ls

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

LS(1)   FSF     LS(1)

NAME
        ls — list directory contents

SYNOPSIS
        ls [OPTION]... [FILE]...

DESCRIPTION
        List  information  about
        the FILEs (the current directory by default).
        Sort entries alphabetically if none
        of -cftuSUX nor --sort.

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

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

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

export PAGER=most

в конфигурациооном файле пользователя.

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

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

$ man -k print

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

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

$ man man

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

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


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

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

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

С навигационными возможностями команды less можно ознакомиться, нажав клавишу h — вызов встроенной её помощи. Из которой мы и узнаем, что перемещаться по man-странице можно с помощью управляющих последовательностей.

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

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

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

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

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

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

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

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

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

Команда sudo

Команда sudo — вторая по важности команда в Mint. Это — основной способ получения прав администратора обычным пользователем. А по умолчанию — так просто единственный, ибо при инсталляции этого дистрибутива пароль root'а не задаётся и, соответственно, доступа к аккаунту «чистого» суперпользователя нет (хотя при желании его можно получить). Команда эта дополняется утилитами visudo и sudoedit. Это узкоспециализированные средства для редактирования общесистемных конфигурационных файлов (в том числе и конфига самой sudo) обычным пользователем. Главные особенности sudo таковы:

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

  2. во-вторых, действие sudo распространяется по умолчанию только на одну команду — ту, которая указывается в качестве ее аргумента; хотя и такое поведение можно изменить с помощью соответствующих опций, о чём будет сказано позднее;

  3. в-третьих, sudo обеспечивает более гибкое разграничение доступа пользователей к административным правам — не только разрешая или запрещая получение оных, но и позволяя выполнять только определённый круг действий.

Этим достигается две цели: а) возможность выполнения пользователем административных действий без сообщения ему суперпользовательского пароля (и даже, как в Mint, при его отсуствтии), и б) снижение риска повредить систему вследствие забывчивости. Да, есть ещё и третья, дополнительная возможность, предоставляемая sudo — протоколирование действий, выполненных в режиме администратора.

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

$ sudo fdisk -l /dev/sda

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

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

Если от лица алминистратора нужно выполнить подряд несколько команд, делать это следует быстро — введенный первый раз пароль по умолчанию «действует» в течении 5 минут. То есть в течении этого времени в ответ на команду sudo пароль повторно запрашиваться не будет.

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

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

$ sudo nano -w /etc/fstab

Впрочем, для редактирования общесистемных конфигов предназначена специальная команда sudoedit (или просто sudo с опцией -e). Она не требует указания имени вызываемого для этой цели редактора: в качестве такового используется значение переменной EDITOR из окружения того пользователя, который ее вызвал. Если эта переменная не определена — а это обычно делается в профильных файлах регистрационной оболочки пользователя, — для редактирования вызывается редактор Vim (в своей упрощенной ипостаси, эмулирующей классический vi).

Как это ни парадоксально, команда sudo не исключает запуска администраторского сеанса внутри обычного пользовательского. Потому что с ее помощью можно запустить su:

$ sudo su

И это — даже в тех дистрибутивах, где root-аккаунта как бы и нет; точнее, по умолчанию нет его пароля (к ним, как уже было сказано, относится Mint). Но и задать пароль «настоящего» администратора не запрещается — для этого достаточно дать команду

$ sudo passwd

чтобы в дальнейшем использовать команду su обычным образом. Правда, не уверен, что это стоит делать. Потому что для перманентного владения правами администратора команда sudo предусматривает «идеологически правильный» метод, и даже не один. Это — опции -s и -i, пролонгирующие, хотя и несколько по разному, действие команды sudo на неопределённый срок, вплоть до завершения «вторичного сеанса» командой exit.

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

Опция же -i полностью воспроизводит root-окружение, запуская его командную оболочку как регистрационную (login shell). Разумеется, при этом и текущий каталог меняется на /root, а приглашение командной строки приобретает вид, описанный в соответствующей переменной профильного файла администраторского шелла, то есть /root/.bashrc. Правда, в Mint и его по умолчанию нет.

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

Возможности sudo не ограничиваются запуском команд от лица администратора: задав опцию -u username, их можно выполнить и от лица того пользователя, чей логин задан в качестве её значения. Это может быть полезным при просмотре или копировании dot-файлов и dot-каталогов другого пользователя, часто открытых для чтения и изменения только их хозяину.

К слову сказать, команду sudo можно запустить так, чтобы она запрашивала пароль пользователя, от имени которого будет выполняться команда (например, администратора), а не того, кто требует его полномочий. Для этого существует опция -targetpw. А чтобы сделать требование root’ового пароля постоянным, достаточно определить, например, псевдоним типа

alias sudo='sudo -targetpw'

Команда sudo имеет ещё немало опций — выше я привёл только те, которые мне приходилось использовать. Остальные легко посмотреть в man sudo. Из не перечисленных упомяну ещё опцию -b, предписывающую запускать «подсудную» команду в фоновом режиме. Она может быть полезна при выполнении долговременных действий, например, при копировании образов USB на флэшку командой dd.

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

  • любой ли пользователь может получить права администратора через команду sudo, и
  • все ли действия по администрированию он может ее посредством выполнить?

Если говорить о семействе Ubuntu (в том числе и дистрибутиве Mint), в котором механизм этот был впервые задействован из «коробки» — то «из коробки» же ответ на первый вопрос будет отрицательным, на второй — положительным. А вообще это зависит от настроек программы sudo, которые описываются в файле /etc/sudoers. И в нем можно задать правила, допускающие к исполнению определенных команд только отдельных пользователей. В обобщенном виде это выглядит так:

username        host = command

Здесь, как нетрудно догадаться, username — имя пользователя, для которого устанавливается данное правило, host — имя машины, с которой он может к этому правилу прибегнуть, command — конкретная команда, использование которой разрешается данному пользователю с данной машины. Команда должна быть дана с указанием полного абсолютного пути (то есть /sbin/fdisk, а не fdisk). Поле описания команд может включать несколько значений, разделенных запятыми, например:

username        ALL = /sbin/fdisk,/bin/mount

В Ubuntu’идах по умолчанию правила доступа пользователей к административным привилегиям описываются так:

# User privilege specificationroot
ALL=(ALL) ALL

# Members of the admin group may gain root privileges
%admin  ALL=(ALL) ALL

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

В более иных дистрибутивах, не использующих sudo «из коробки», потребуется редактирование её конфигурационного файла — того самого /etc/sudoers, о котором упоминалось выше.

Файл /etc/sudoers — обычный текстовый, и, соответственно, его можно редактировать в любом текстовом редакторе (или, скажем, средствами ed или sed). Однако при этом существует определённый риск что-нибудь напортачить (за счёт обычных опечаток), вплоть до того, что полностью закрыть самому себе доступ к привилегиям суперпользователя. Конечно, ситуации эти поправимы — например, через перезагрузку в однопользовательском режиме. Однако, лучше в них не попадать. И потому более надёжным средством модификации /etc/sudoers будет использование специально предназначенной для того утилиты — visudo.

Утилита visudo не делает ничего сверхъестественного — она просто открывает /etc/sudoers в текстовом редакторе, описываемом переменной EDITOR суперпользователя (если таковая не определена, им будет опять же классический vi — отсюда и название) и позволяет его отредактировать обычным образом, после чего выйти из редактора с сохранением результатов штатными его средствами. Однако перед этим результат редактирования проверяется на корректность. И если обнаруживается нарушение синтаксиса, принятого для /etc/sudoers, выдается соответствующее предупреждение. После которого можно вернуться к редактированию, отказаться от сделанных изменений или все-таки принять их (разумеется, под личную ответственность).

Утилита visudo не гарантирует стопроцентного успеха редактирования. Так как проверяет только соответствие синтаксиса, но не «правильность самих правил». То есть если ошибка будет допущена в указании пути к нужной для данного правила команде — эта команда через sudo не сработает.

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

%admin        ALL=(ALL)       NOPASSWD: ALL

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

Defaults    timestamp_timeout=10

где значение таймаута указано в минутах. Если же изменить его на ноль --

Defaults    timestamp_timeout=0

то пароль будет запрашиваться каждый раз при обращении к команде sudo.

Можно, напротив, отключить тайаут на действие sudo, ввдя для него отрицательное значение:

Defaults    timestamp_timeout=-1

В этом случае пароль будет запрошен только при первом вызове этой команды.

Более пристальное вглядывание в файл /etc/sudoers легко подскажет возможности дать определённым пользователям или группам только ограниченный набор прав. Впрочем, тут уже начинаются тонкости всамделишнего администрирования.

Создание файлов и каталогов

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

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

$ touch filename

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

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

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

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

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

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

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

$ cat > filename

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

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

$ ls dir | tee filename

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

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

Аттрибуция файлов

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

Команды 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 предназначена для смены атрибутов доступа — чтения, изменения и исполнения. В отношении единичного файла делается это просто:

$ 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.

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

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

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

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

Так вот, для изменения временных атрибутов файлов и предназначена в первую очередь команда 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/vboxhost-4.3.20, являющий собой символическую ссылку на каталог /usr/share/virtualbox/src/vboxhost, то в ответ на

$ pwd -L

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

/usr/src/vboxhost-4.3.20

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

$ pwd -P

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

/usr/share/virtualbox/src/vboxhost

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

$ locate zsh

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

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

и так далее. Команда mlocate действует точно так же. Обе они при этом обращаются к базе данных /var/lib/mlocate/mlocate.db. Исходно эта база данных пуста — и перед использованием команды locate или mlocate должна быть наполнена содержанием. Для этого предназначен сценарий /usr/bin/updatedb.mlocate. Он извлекает сведения из базы данных установленных пакетов — /var/lib/dpkg. В Mint этот сценарий автоматически запускается ежедневно посредством cron, обеспечивая регулярное обновление поисковой базы.

Информация о файлах

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

Общая форма запуска команды 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). К слову сказать, она также аннулирует действие опции -i, например, при использовании ее в псевдониме команды cp.

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

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

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

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

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

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

$ dd [options]

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

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

  • опции ibs=n и obs=n устанавливают размер блока для входного и выходного потоков, bs=n — для обоих сразу;
  • опция skip=n указывает, сколько блоков нужно пропустить перед записью входного потока;
  • опция count=n предписывает скопировать из входного потока лишь указанное количество блоков, отсчитываемых с начала файла-источника.

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

Архивация…

Для пользователя Windows, привыкшего к программам типа Zip/WinZip и Rar/WinRar, архивация и компрессия неразрывны, как лошади в упряжке. Однако это — вполне разные действия.

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

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

Традиционное и самое распространённое средство архивации в 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 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.

Здесь уместно добавить пару слов об утилите ar, предназначенной для создания архивов, их модификации, частичной экстракции из них файлов и полного развёртывания. Подобно tar, это — чистый архиватор, не выполняющий никакой компрессии. И, насколько я знаю, практически не используемый для архивирования данных, в частности, для резервного копирования. Но исторически сложилось так, что именно утилитой ar в конечном счёте упаковываются компоненты пакетов deb-формата, используемого в Mint (и многих других дистрибутивах).

… и компрессия

Утилита 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

Использование этой команды настолько прозрачно, что я задерживаться на ней не буду. Замечу только, что утилита gzip интегрирована в архиватор tar, вызвываясь из него опцией z. То есть для создания компрессированного архива потребуется такая команда:

$ tar czf archive.tar.gz path2/

А для декомпрессии и развёртывания архива — такая:

$ tar xzf archive.tar.gz

В обоих случаях не принесёт вреда добавление опции v — она обеспечит вывод на экран сообщеня о ходе процесса.

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

Итоговый компрессированный файл получает имя вида *.bz2 и может быть распакован командой bunzip2 (или командой bzip2 -d). Следует только помнить, что форматы *.gz и *.bz2 не совместимы между собой. Соответственно, первый не может быть распакован программой bunzip2, и наоборот.

Как и gzip, утилита bzip2 может быть вызвана из архиватора tar — при создании компрессированного архива так:

$ tar cjf archive.tar.bz2 path2/

А при развёртывании оного — эдак:

$ tar xjf archive.tar.bz2

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

Реализацией формата xz является набор утилит XZ Utils, основанный на алгоритме LZMA (Lempel-Ziv-Markov chain-Algorithm). Сам по себе метод сжатия LZMA существует достаточно давно: он был разработан нашим соотечественником Игорем Павловым с использованием достижений предшественников, разработавших алгоритмы LZ77, LZ78 и LZV — что, впрочем, могло бы составить предмет отдельной истории, которую когда-нибудь кто-нибудь напишет.

А метод LZMA был задействован его автором в собственной же разработке — утилите компрессии 7-Zip для Windows, быстро снискавшей славу несравненного «сжимателя» файлов. Инструментарий для разработки программ, использующих данный метод (LZMA SDK) распространялся сначала на условиях лицензии GPL, что позволяло использовать его в соответствующих проектах (например, в GNU tar). Однако в конце 2008 года Игорь Павлов превратил его в общественное достояние (Public Domain). Вслед за чем был создан основанный на этом методе пакет LZMA Utils, немедленно встроенный в tar. Что сделало этот метод компрессии столь же простым и обыденным, как gzip или bzip2. И с тех пор эта возможность, после установки соответствующего пакета, присутствует во всех дистрибутивах Linux.

Правда, вслед за тем появился LZMA2, улучшенная версия того же алгоритма, обеспечивающий более высокую степень сжатия и лучшую поддержку многопоточности. А на его основе был создан пакет XZ Utils — именно он в настоящее время используется в Mint по умолчанию. И включает в себя такие команды:

  • xz — компрессор и, при указании опции --decompress, декомпрессор;
  • unxz — собственно декомпрессор;
  • xzcat осуществляет декомпрессию на стандартный вывод;
  • xzmore и xzless — pager'ы для lzma-компрессированных текстовых файлов;
  • xzgrep, xzegrep, xzfgrep — поиск текстовых фрагментов в xz-компрессированных файлах.

Последние три утилиты работают аналогично командам xzgrep, xzegrep, xzfgrep, применённым к некомпрессированным файлам. А команда xzcat является аналогом утилиты cat. Об этих четырёх командах будет подробно говориться в ближайших разделах.

Утилиты пакета XZ Utils могут, с некоторыми ограничениями, работать с файлами, запакованными старым методом LZMA1 (но не наоборот). Хотя сами по себе пакеты XZ Utils и LZMA Utils между собой конфликтуют.

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

$ tar --create --xz --file filename.tar.xz path2/arch_dir

Или, в более употребимой простыми людьми краткой форме, так:

$ tar cJf filename.tar.lzma path2/arch_dir

где опция J и представляет собой алиас для полной формы -xz. Если присваивать архивному файлу суффикс по правилам утилиты tar, опцию J можно заменить на a (что эквивалентно полной форме --auto-compress), обеспечивающей определение типа компрессии по «расширению» *.xz. Более того, скажу по секрету: если архив именован по правилам, то можно опустить даже опцию --auto-compress — она и так будет задействована по умолчанию.

Распаковка xz-компрессированного архива выполняется в обратном порядке:

$ tar xJf filename.tar.xz

или

$ tar xaf filename.tar.xz

Метод LZMA и особенно LZMA2 вследствие эффективности компрессии быстро нашёл себе применение в сборке дистрибутивных пакетов: именно с его помощью в настоящее время сжимаются deb-пакеты Mint (и всех других дистрибутивов, использующих этот формат пакетов).

Утилита find и xargs при ней

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

Итак, апофеоз командного файлового менеджмента — утилита 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. Она определяется как построитель и исполнитель командной строки со стандартного ввода. А поскольку на стандартный ввод может быть направлен вывод команды findxargs воспримет результаты ее работы как аргументы какой-либо команды, которую, в свою очередь, можно рассматривать как аргумент ее самоё (по умолчанию такой командой-аргументом является /bin/echo).

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

$ sysctl -a | grep kern.argmax

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

Команды обработки текстов: введение

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

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

Однако круг объектов таких команд не столь уж узок, как может показаться. Ведь именно в виде обычных текстовых файлов в ОС POSIX-семейства хранится масса общесистемной информации, исполняемых сценариев, баз данных атрибутов самых разных объектов. Далее - собственно нарративные тексты любого содержания: ведь чисто текстовый формат для них куда роднее, чем всякого рода *.doc и *.rtf. Ну и никем не возбраняется использовать такие команды в отношении текстов с разметкой - HTML ли, XML, TeX или ещё чего. Так что поле приложения рассматриваемых команд - достаточно обширно.

Просмотр файлов

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

$ cat < filename

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

Недостаток команды cat как средства просмотра - невозможность перемещёния по телу файла: выведя содержимое, она завершает свою работу. Конечно, «пролистывание» выведенного возможно, но - средствами системной консоли, а не самой команды.

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

В Unix-системах имеется две основные программы pager'а - more и less. Первая из них допускает только однонаправленный (вперед) просмотр и имеет слабые интерактивные возможности. Почему ныне и представляет лишь исторический интерес, так что о ней я говорить не буду. Тем более, что в современных свободных POSIX-системах она как таковая отсутствует: файл с именем /usr/bin/more, который можно обнаружить во FreeBSD и некоторых дистрибутивах Linux, на самом деле представляет собой жёсткую или символическую ссылку на ту же самую программу, что и команда less. Хотя эта программа проявляет несколько различные свойства, в зависимости от того, какой из указанных команд она вызвана, функции ее от этого не меняются. Так что дальше я буду говорить только о команде less.

Самый простой способ вызова команды

$ less filename

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

command.txt 3%

что, однако, не воспрещает и здесь давать ее встроенные команды — вводом символа двоеточия (:) и закрепленной за командой литеры (или их сочетания).

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

  • с помощью стандартных клавиш управления курсором: PageDown или Spacebar (вперед на один экран), PageUp (назад на один экран), Down или Enter (вперед на одну строку), Up (назад на одну строку), Right (на пол-экрана вправо), Left (на пол-экрана влево);
  • с помощью предопределенных клавишных комбинаций, сходных с управляющими клавиатурными последовательностями командных оболочек и таких текстовых редакторов, как emacs и joe (хотя и не всегда с ними совпадающими): Control+V (на один экран вперед), Escape-V (на один экран назад), Control+N (на одну строку вперед), Control+P (на одну строку назад);
  • с помощью фиксированных символьных клавиш, иногда подобных таковым командного режима редактора vi: z и w (вперед и назад на один экран), e и y (вперед и назад на одну строку, можно использовать также привычные по vi клавиши j и k, соответственно), d и u (вперед и назад на пол-экрана).

Последний способ интересен тем, что допускает численные аргументы перед символьной командой: так, нажатие 3e приведет к перемещёнию на три строки вперед, а 2w - на два экрана назад.

Помимо «плавной», так сказать, навигации, можно перемещаться по файлу и скачками (jumping): нажатие клавиши с символом g (или последовательности Escape-<) позволяет переместиться к первой строке файла, клавиши G (регистр важен! дублирующий вариант - Escape->) - к последней его строке, клавиши p - к началу файла.

Кроме навигации, имеется и возможность двустороннего поиска - в направлении как конца, так и начала файла. Для поиска вперед требуется ввести символ прямого слэша (/) и за ним - искомое сочетание символов. Поиск в обратном направлении предваряется символом вопроса (?). В обоих случаях в шаблоне поиска можно использовать стандартные регулярные выражения *, ?, [список_символов] или [диапазон_символов]. Нажатие клавиши n (в нижнем регистре) приводит к повторному поиску в заданном ранее направлении, клавиши N (в верхнем регистре) - к поиску в направлении противоположном.

Управляющие комбинации команды less могут быть переопределены с помощью команды lesskey. Формат ее

$ lesskey -o output input

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

#command
\r        forw-line
\n        forw-line
...
k         back-line
...

Выходные данные - создаваемый из текстового двоичный файл, который собственно и используется командой less. Стандартное для него имя - ~/.less.

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

$ less file1 file2 ... file#

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

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

command.html (file 2 of 10) lines 1-29/1364 2%

Значение команд постраничного просмотра файлов ещё и в том, что именно с их помощью осуществляется доступ к экранной документации (man-страницам). Команда

$ man cmd_name

как было описано в предыдущей интермедии, на самом деле вызывает определенный по умолчанию pager для просмотра соответствующего файла /usr/share/man/man#/cmd_name.gz. Какой именно - определяется переменной PAGER в пользовательских настройках.

Кроме команд постраничного просмотра, существуют команды для просмотра фрагментарного. Это - head и tail, выводящие на экран некоторую фиксированную порцию файла, указанного в качестве их аргумента, с начала или с конца, соответственно. По умолчанию эта порция для обеих команд составляет десять строк (включая пустые). Однако ее можно переопределить произвольным образом, указав опции -n [число_линий] или -c [количество_байт]. Например, команда

$ head -n 3 filename

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

$ tail -c 100 filename

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

Существуют и средства просмотра компрессированных файлов. Для файлов, сжатых программой gzip, можно использовать команды zcat и zmore, для спрессованных командой bzip2 - команду bzcat. Использование их ничем не отличается от аналогов для несжатых файлов - в сущности, именно они и вызываются для обеспечения просмотра. В случае команды zmore, как нетрудно догадаться, на самом деле используется команда less (сама по себе она аналога для компрессированных файлов не имеет).

Сравнение, объединение и деление файлов

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

$ cmp file1 fil2

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

file1 file2 differ: char 27, line 4

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

Более информативный вывод обеспечивает команда diff. Она также осуществляет построчное сравнение двух файлов, но выводит список строк, в которых обнаружены отличия. Например, для двух файлов вида

$ less file1

line 1
line 2
line 3
line 4
line 5

и

$ less file2

line 1
line 2
line 3
line 3a
line 4
line 5

это будет выглядеть следующим образом:

$ diff file1 file2

3a4
> line 3a

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

  • a (от append) указывает на строку, отсутствующую в первом файле, но присутствующую во втором;
  • c (от change) фиксирует строки с одинаковым номером, но разным содержанием;
  • d (от delete) определяет строки, уникальные для первого файла.

То есть в данном примере для преобразования file1 в file2 в него после строки 3 должна быть вставлена строка 4 из второго файла, что символизирует вторая линия блока - > line 3a, где > означает строку из первого сравниваемого файла. Если же аргументы команды diff дать в обратном порядке, вывод ее будет выглядеть следующим образом:

$ diff file2 file1

4d3
< line 3a

показывающим, что для достижения идентичности из file2 должна быть удалена четвертая строка (< line 3a, где < означает строку из второго файла). Если же произвести сравнение file1 с file3, имеющим вид

$ less file3

line 1
line 2
line 3a
line 4
line 5

то вывод команды

$ diff file1 file3

3c3
< line 3
---
> line 3a

будет означать необходимость замены третьей строки из file1 (символ <) на третью строку из file3 (символ >).

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

diff -c file1 file2

*** file1       Sun May 12 11:44:44 2002
--- file2       Mon May 13 15:17:27 2002

***************

*** 1,5 ****

--- 1,6 ----

  line 1
  line 2
  line 3
+ line 3a
  line 4
  line 5

Количество строк контента задается опцией -C:

diff -C 1 file1 file2                                      ttyv1

*** file1       Sun May 12 11:44:44 2002
--- file2       Mon May 13 15:17:27 2002

***************

*** 3,4 ****

--- 3,5 ----

  line 3
+ line 3a
  line 4

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

Кроме контекстного формата, используется ещё и вывод в унифицированном формате, что предписывается опцией -U [значение], в качестве значения указывается число строк. В нем для обозначения изменяемых строк используются только символы + и -, а сам вывод чуть короче, чем при использовании контекстного формата.

С помощью многочисленных опций команды diff сравнение файлов может быть детализовано и конкретизировано. Так, опция -b предписывает игнорировать «пустые» символы пробелов и табуляции в конце строк, а опция -w - вообще «лишние» пробелы (и те, и другие обычно имеют случайное происхождение). При указании опции -B игнорируются пустые строки, то есть не содержащие никаких иных символов, кроме перевода каретки; строки с символами табуляции или пробела как пустые не рассматриваются, для их игнорирования требуется опция -w. Благодаря опции -i при сравнении не принимается во внимание различие регистров символов, а опция -I regexp определяет регулярные вырвжения, строки с которыми также игнорируются при сравнении.

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

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

В принципе, команда diff и придумана была именно для сравнения файлов исходников, над которыми ведут работу несколько (в пределе - неограниченное количество, как в случае с Linux) человек. Однако невозбранно и ее использование в мирных целях - то есть для сравнения просто повествовательных текстов. Единственное, что следует понимать при этом абсолютно ясно - то, что diff выполняет именно построчное сравнение. То есть: сравнение последовательностей символов, ограниченных символами конце строки с обеих сторон. И, соответственно, непрерывная абзацная строка в стиле emacs и vi - совсем не то же самое, что строка, образуемая в редакторе joe на границе экрана. Впрочем, это - вопрос, к которому ещё не раз придется возвращаться.

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

Существуют и средства для сравнения сжатых файлов. Это - zcmp и zdiff. Подобно командам просмотра, ими просто вызываются соотвествтующие команды cmp и diff. И потому использование их не имеет никаких особенностей.

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

$ cat file1 file2 ... file# > file_all

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

А вот команда patch выступает в качестве диалектической пары для команды diff, именно она вносит в файл те изменения, которые документируются последней. Выглядит эта команда примерно так:

$ patch file1 diff_file

в ответ на что последует нечто вроде следующего вывода:

Hmm...  Looks like a normal diff to me...
Patching file file1 using Plan A...
Hunk #1 succeeded at 4.
done

В результате исходная версия file1 будет сохранена под именем file1.orig, а сам он преобразован в соответствие с описанием diff-файла. Возможна и форма

patch < diff_file

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

В качестве второго аргумента команды patch могут использоваться dif-файлы не только в стандартном, но и в контекстном или унифицированном формате. Это следует указать посредством опции -c или -u, соответственно.

Сочетание команд diff и patch очень широко используется при внесении изменений в исходные тексты программы. В частности, они применяются для внесения дистрибутив-специфичных изменений в deb-пакеты репозиториев Ununtu и Mint.

Не менее часто, чем в слиянии, возникает и необходимость в разделении файлов на части. Цели этой служит команда split. Формат ее:

$ split [options] filename [prefix]

В результате исходный файл будет разбит на несколько отдельных файлов вида prefixaa, prefixab и так далее. Значение аргумента prefix по умолчанию - x (то есть без его указания итоговые файлы получат имена xaa, xab и т.д.).

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

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

$ split -b 1474560 arch_name

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

$ split -b 650m arch_name

архив можно подготовить к записи на носители CD-R/RW. Легко догадаться, что обратное слияние таких фрагментированных файлов можно выполнить командой cat.

В BSD-реализации команды split имеется опция -p (от pattern — шаблон), благодаря которой файл может быть разделена на фрагменты, ограниченные строками, содержащими текст, приведенный в качестве значения шаблона. Linux-реализация команды split таким свойством не обладает. Однако взамен этому в Linux есть команда csplit, именно для разделения файла по шаблону и предназначенная.

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

$ csplit -f chapter mybook.txt '/Глава/' {23}

Здесь опция -f задаёт маску имён файлов, на которые будет разбит исходный текст (то есть файл mybook.txt). Шаблон, по которому будет выполняться разбиение — слово Глава ограничено прямыми слэшами и заключено в «строгие» кавычки. А число в фигурных скобках указывает, сколько раз надо повторить процедуру разбиения по заданному шаблону. И в результате мы получаем серию файлов вида chapter##, где файл chapter00 будет включать текст от начала до первой строки со словом Глава (которая, как ни странно, будет главой первой), chapter01 — от строки Глава первая до Главы второй, и так далее. Исходный файл при этом останется в неприкосновенности.

Поиск в файлах: grep сотоварищи

В одном из предыдущих разделов говорилось о поиске файлов посредством команды find. Ныне же речь пойдет о поиске внутри файлового контента - то есть поиске текстовых фрагментов. Для этого в POSIX-системах используется семейство утилит grep — собственно grep, egrep и fgrep, несколько различющихся функционально. Впрочем, в большинстве систем все это суть разные имена (жёсткие ссылки) одной и той же программы, именуемой GNU-реализацией grep, включающей ряд функций, свойственных ее расширенному аналогу, egrep. Соответственно, поиск текстовых фрагментов в файлах может быть вызван любой из этих команд, хотя в каждом случае функциональность их будет несколько различаться.

Однако начнем по порядку. Самой простой формой команды grep является следующая:

$ grep pattern files

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

$ grep line ./*

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

Шаблоны могут включать в себя регулярные выражения. причём список таковых для команды grep существенно шире, чем для команд манипулирования файлами. Так, кроме маски любой последовательности символов (*), любого одиночного символа (?), списка и диапазона символов ([a...z] и [a-z]), могут встречаться:

  • . (точка) - маска любого одиночного (но, в отличие от маски ?, обязательно присутствующего) символа; то есть при задании шаблона lin. будут найдены строки, содержашие line или lins, но не lin;
  • ^ и $ - маски начала и конца строки, соответственно: по шаблону ^line, будут найдены строки, начинающиеся с соответствующего слова, по шаблону же line$ - им заканчивающиеся;
  • маски повторения предыдущего шаблона, \{n\} - ровно n раз, \{n,\} - не менее n раз, \{,m\} - не более m раз, \{n,m\} - не менее n раз и не более m раз.

Маски повторения относятся к так называемым расширенным регулярным выражениям. Для их использования команда grep должна быть дана с опцией -e или в форме egrep — последняя часто определяется в общесистемном или пользовательском профильном файле как псевдоним команды grep:

alias grep='egrep -s'

или

alias grep egrep

в оболочках семейств shell и csh, соответственно.

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

$ grep -e pattern1 -e pattern2 files

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

$ egrep 'pattern1|pattern2' files

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

$ egrep 'Часть|Глава|Раздел|Параграф' filename

Для текста, включающего html- или TeX-разметку, роль рубрик могут выполнять соответствующие ее элементы, например:

$ egrep ' <h1>|<h2>|<h3>|<h4>' filename

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

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

$ egrep '<h1>|<h2>|<h3>|<h4>' path/*.html > sitemap.html

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

Часть|Глава|Раздел|Параграф

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

$ egrep -f shablon filename

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

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

Команда grep имеет и аналоги для поиска в сжатых файлах - команду zgrep и семейство команд xzgrep, о которых говорилось в миниочерке про архивацию и компрессию.

В дистрибутиве Mint имеется фирменная утилита для поиска текстовых фрагментов в файлах — search. Она входит в состав пакета mintsystem (о котором подробней говорится в очерке об утилите apt) и располагается в каталоге /usr/local/bin/. Формат её вызова таков:

$ search for [искомый фрагмент] in [каталог для поиска]

Например, в форме

$ search for 'дистрибутив Mint' in /home/current/alv.me

она отыщет все абзацы с вхождением дистрибутив Mint во всех файлах указанного каталога и выведёт их в таком виде:

/home/current/alv.me/mint/mint17-cin/mint17-02.txt:9:В числе родственников... нет, не примазавшихся, а настоящих, но пошедших другим путём, был и дистрибутив Mint. Сейчас не время обсуждать его взаимоотношения с прародительской Ububtu, но программу установки он унаследовал от неё практически без изменений. По крайней мере, до недавнего времени макроскопических различий в инсталляторах этих систем не наблюдалось.

...

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

Sed: средство потокового редактирования

Весьма часто при обработке текстов встает такая задача: заменить одно слово (или последовательность слов) на другое сразу во многих файлах. Как она решается «подоконными» средствами? Обычно - открытием всех подлежащих изменению документов в word-процессоре и применением функции поиска/замены последовательно в каждом из них.

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

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

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

$ sudo sed -i 's/NoDisplay=true/NoDisplay=false/' /etc/xdg/autostart/*

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

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

В самом общем виде sed требует двух аргументов — указания встроенной его команды и имени файла, к которому она должны быть применена. Впрочем, в качестве аргумента можно задать только простую команду, мало-мальски сложное действие (а команды поиска/замены принадлежат к числу сложных) необходимо определить через значения опции -e, заключенные в кавычки (одинарные или двойные - по ситуации). Что же касается имен файлов — то их в качестве аргументов можно указать сколько угодно, в том числе и с помощью масок типа *, *.txt и так далее. Правда, sed не обрабатывает содержимое вложенных подкаталогов, но это — дело поправимое (как — скоро увидим). Так что поиск и замена слова или их последовательности выполняются такой конструкцией:

$ sed -e 's/Вася Пупкин/Петя Лавочкин/' *

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

По умолчанию sed выводит результаты своей работы на стандартный вывод, не внося изменений в файлы аргументы. Так где же здесь редактирование? Оно обеспечивается другой опцией - -i, указание которой внесет изменения непосредственно в обрабатываемый файл. В результате команда для замены, например, всех вхождений html на shtml во всех файлах текущего каталога будет выглядеть так:

$ sed -i -e 's/html/shtml' *

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

$ find . -name * -exec sed -i -e 's/html/shtml' * {} \

она с успехом справится с этой задачей.

Я привел лишь элементарные примеры использования sed. На самом деле возможности его много шире, но их описание далеко выходит за рамки этого краткого введения.