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

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

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

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

В самом подключаемом модуле для обработки HTML-файлов (html.plg) я также добавил функций: теперь он может работать с файлами не только в кодировке Windows-1251, но и в KOI8-R (перекодируя возвращаемый заголовок в Windows-1251). Причем если кодировка не указана в HTML-документе явно, модуль пытается определить ее автоматически, считая, что это Windows-1251 или KOI8-R и используя простейший алгоритм, описанный в работе [2] (см. также комментарий в тексте модуля).

Само собой разумеется, что после таких нововведений расширение имени файлов, обрабатываемых программой, уже не могло быть ограничено одним htm, ведь появилась возможность обработки и других типов (да и HTML-файлы могут быть названы по-другому: html, phtml, shtml...). Поэтому я предусмотрел указание одного или нескольких расширений в командной строке, а для обработки файлов с любыми расширениями можно задать символ «звездочка» («*»).

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

Когда по каким-либо причинам нужно сохранить оригинальные имена файлов (скажем, если есть группа HTML-файлов, связанных гиперссылками, ведь при переименовании файлов гиперссылки станут недействительными) и вместе с тем требуется быстро узнать, что содержится в каждом из них, то предусмотрена возможность вместо переименования добавлять в качестве описаний извлеченные из файлов заголовки в файл descript.ion, находящийся в одном с ними каталоге (если такого файла нет, он создается). Напомню, что файл descript.ion — текстовый, содержащий однострочные описания файлов, находящихся в одном с ним каталоге. В таких файловых менеджерах, как Far, Dos Navigator, можно включить такой режим отображения, при котором рядом с именами файлов показываются их описания, взятые из этого файла. (На рисунке показано, как это выглядит в Dos Navigator при максимизированной текущей панели.)

Также был разработан подключаемый модуль mmodules.plg, возвращающий название композиции, извлеченное из музыкального файла в формате IT, XM, S3M, MOD. Да, уже существует специальная программа для переименования музыкальных файлов в соответствии с названиями композиций (MODNamer by Mauro ?DjM? Molinari), поддерживающая к тому же больше типов файлов. Но работа описываемой программы с вышеупомянутым подключаемым модулем имеет свои преимущества: передача аргументов через командную строку, отмена переименования, добавление названия композиций в качестве описаний файлов в файл descript.ion. (Текст описываемой программы см. на «Мир ПК-диске».)

Руководство пользователя

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

[имя файла с интерпретатором Perl] [имя файла с программой]

{опции} [имя файла с подключаемым модулем] [имя обрабатываемого файла]

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

[имя каталога с обрабатываемыми файлами] [список расширений обрабатываемых файлов]

Список расширений состоит из элементов, разделенных запятой (без пробела), например ?htm,html,shtml?. В нем может быть указано и пустое расширение: так, ?,htm? определяет пустое расширение и ?htm?. В списке может быть, конечно, и только одна звездочка («*»), соответствующая любой комбинации символов. Нельзя задать расширение, содержащее запятую (но, будем надеяться, вы с такой ситуацией не столкнетесь). кавычки вокруг списка обязательны, если он состоит лишь из пустого расширения (т.е. ??) или какое-то из расширений содержит пробел.

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

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

Опция «/r» — рекурсивный обход подкаталогов. Позволяет обработать сразу целую ветвь их дерева.

Опция «/n» — не создавать файл «_back_rename.pl» (это perl-скрипт для отмены переименования). По умолчанию программа создает его в каждом каталоге, где она переименовала хотя бы один файл. Причем если в каталоге уже есть такой сценарий, программа не перезапишет его, а создаст с именем «_back_rename (1).pl» и т.д. Таким образом, возможна многошаговая отмена.

Кстати, почему для обратного переименования создается именно perl-скрипт, а не просто bat-файл? Все дело в проблемах с кодировками. Внутри программы (и внутри создаваемого сценария на Perl) имена файлов хранятся в кодировке Windows-1251, а в bat-файлах используется кодировка CP866. Точное перекодирование же из Windows-1251 в CP866 невозможно из-за того, что наборы символов в этих кодировках не совпадают.

Опция «/d» — вместо переименования файлов программа будет добавлять их заголовки в качестве описаний в файл descript.ion, находящийся в каталоге с обрабатываемыми файлами (когда такового файла нет, то он создается). Конечно, если программа обрабатывает не один каталог (т.е. была указана опция «/r»), то в каждом каталоге будет свой файл descript.ion.

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

При переименовании файлов программа при необходимости укорачивает извлеченный из файла заголовок так, чтобы его длина не превышала максимальную (по умолчанию — 128 символов, это значение хранится в переменной $max_n_len). Кроме того, если в заголовке содержатся символы, недопустимые в имени файла ( / : * ? « < > |, а также символы с кодами 0-31), они будут заменены на «_».

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

В случае добавления описания в файл descript.ion программа при необходимости укорачивает описание, чтобы его длина не превышала максимальную (по умолчанию — 256 символов, это значение хранится в переменной $max_d_len). Если в заголовке содержатся символы, недопустимые в описании (таковыми считаются символы с кодами 0,10,13), они будут заменены на «_».

Так как кодировка файла описаний — CP866, а в программе описания хранятся в Windows-1251, то при записи они перекодируются. Причем вместо символов, которые программа не смогла перекодировать, записываются псевдографические символы в виде заштрихованных прямоугольников.

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

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

И еще об описаниях. Чтобы в Dos Navigator включить в текущей панели режим просмотра описаний файлов, надо нажать +<;>. А чтобы максимизировать панель (см. рисунок) — ++. Можно вернуться к исходному режиму отображения, повторно нажав эти комбинации клавиш. Если вы применяете Far, то для включения режима «Описания» используйте комбинацию левая клавиша +6, а для включения режима «Длинные описания» — левая +7; вернуться же к обычному режиму отображения («Средний режим») можно с помощью левой +2.

Ниже приведен пример сеанса работы с программой, когда выполняется переименование файлов.

C:WORKTEST>perl c:m_renamem_rename.pl
 c:m_renamehtml.plg . htm
Multi-Rename 1.0 (c) Ivan Roshin, Moscow, 11 Oct 2005
E-mail: bestview@mtu-net.ru WWW: http://www.ivr.da.ru
Renaming file(s):
.1.htm => Заголовок.htm
.2.htm <= Error: title not found!
.3.htm <= Error: plugin can't recode title
 from utf-8 to Windows-1251!
Creating backup file "_back_rename.pl"... OK!
Processed: 3 file(s), OK: 1, not OK: 2.

И еще пример для случая, при котором программа не переименовывает файлы, а записывает их описания в файл descript.ion.

C:WORKTEST>perl c:m_renamem_rename.pl /d
 c:m_renamehtml.plg. htm
Multi-Rename 1.0 (c) Ivan Roshin, Moscow, 11 Oct 2005
E-mail: bestview@mtu-net.ru WWW: http://www.ivr.da.ru
Getting description(s):
.1.htm: Заголовок
.2.htm <= Error: title not found!
.3.htm <= Error: plugin can't recode title
 from utf-8 to Windows-1251!
Writing to "descript.ion"... OK!
Processed: 3 file(s), OK: 1, not OK: 2.

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

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

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

Подключаемые модули

Модуль, подключаемый к программе, представляет собой текстовый файл с расширением plg (оно не проверяется и в принципе может быть другим). Первая строка данного файла служит для его идентификации и должна быть такой: «# Plugin for Multi-Rename 1.0». Программа проверяет эту строку, дабы не загрузить что-то другое, если пользователь ошибся при указании имени. Как я уже упоминал, в модуле находится тело подпрограммы get_title, которая получает имя обрабатываемого файла, анализирует его содержимое и в итоге должна вернуть такие три значения:

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

Все используемые в подключаемом модуле переменные должны быть в нем объявлены, чтобы предотвратить возможный конфликт с одноименными переменными основной программы (да и директива «use strict» требует, чтобы все используемые переменные были объявлены).

Ниже приведены исходные тексты двух подключаемых модулей, о которых я уже рассказывал выше: html.plg (для обработки HTML-файлов) и mmodules.plg (для обработки музыкальных модулей). Они также могут пригодиться в качестве примеров при создании собственных подключаемых обработчиков текстовых или двоичных данных. (Их текст см. на «Мир ПК-диске».)

Как еще можно использовать программу

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

Рассмотрим пример. Пусть исходные имена файлов выглядят так:

11062005.txt, 15062005.TXT и т.п., т.е. имя обозначает некую дату (первые две цифры — число, вторые две — месяц, оставшиеся четыре — год), а расширение — txt без учета регистра букв.

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

Подключаемый модуль (файл date.plg) будет таким:

# Plugin for Multi-Rename 1.0
# (c) Ivan Roshin, Moscow, 27 Sep 2005.
if ($_[0]=~m/(?:^|[:])(d{2})(d{2})(d{4}).txt$/i)
{
return (0,$3.$2.$1,'');
}
else
{
return (1,'','Error: incorrect name!');
}

Как он работает? Сначала с помощью регулярного выражения проверяется, состоит ли имя файла из восьми цифр и есть ли расширение txt, без учета регистра. Если все окажется так, как требуется, то во встроенных переменных $1, $2 и $3 запомнятся извлеченные из имени число, месяц и год соответственно и модуль возвратит в качестве заголовка, извлеченного из файла, строку, состоящую из этих переменных, расположенных уже в обратном порядке (год, месяц, число). Если же ему было передано не такое имя файла, как предполагалось по условиям задачи, то он выдаст сообщение об ошибке. Как видим, все довольно просто.

Еще один пример. Допустим, в некоем каталоге находятся файлы, имена которых различаются только числом в конце — порядковым номером файла (в частности, имя может состоять лишь из порядкового номера), например, name1, name2, ..., name38. Если при просмотре каталога файлы упорядочиваются по именам, то их порядок будет таким: name1, name10, name11... А хотелось бы видеть «естественный» порядок, при котором файлы располагаются по возрастанию своих номеров. Дабы добиться желаемого, следует изменить порядковые номера в именах файлов так, чтобы во всех этих номерах было одинаковое число цифр. Тогда упорядочение файлов по именам даст нужный результат: name01, name02, ..., name38.

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

Именно такое изменение имени файла и производит приведенный ниже подключаемый модуль (файл ext_num. plg). Перед его использованием следует установить в тексте желаемое значение n в качестве начального значения переменной $n.

# Plugin for Multi-Rename 1.0
# (c) Ivan Roshin, Moscow, 27 Sep 2005.
my $n=2;  # До скольких цифр расширять порядковый номер.
my $name; # Имя обрабатываемого файла (без пути, без
# расширения).
# Получаем $name.
if ($_[0]=~m/([^:]*).[^:]*$/) # Если есть расширение.
{
$name=$1;
}
else # Пустое расширение.
{
$_[0]=~m/[^:]*$/;
$name=$&;
}
# Обрабатываем $name.
if ($name=~m/d+$/) # Если есть цифры в конце.
{
my $k=$n-length($&);
if ($k>0) # Если надо добавлять нули.
{
$name=~s/d+$/('0'x$k).$&/e;
}
return (0,$name,'');
}
else # Некорректное имя: не содержит цифр в конце.
{
return (1,'','Error: incorrect name!');
}

Источники

  1. Спецификация HTML 4.01. http://pyramidin.narod.ru/html401/index.htm
  2. Рощин И. Добавление в HTML-документы информации об их кодировке//Радиомир. Ваш компьютер. 2003. №10. http://ivr.webzone.ru/articles/charset
  3. Описание формата модулей Impulse Tracker. Файл ittech.txt из комплекта поставки Impulse Tracker 2.14.
  4. XM module format description for XM files version $0104. Файл xm.txt из комплекта поставки Fast Tracker 2.09.
  5. Scream Tracker 3.20 File Formats And Mixing Info. Файл tech.doc из комплекта поставки Scream Tracker 3.21.
  6. Anodyne. Формат MOD-файлов и технология их воспроизведения. http://www.codenet.ru/progr/formt/mod1.php

ОБ АВТОРЕ

Иван Рощин — автор более 80 статей и ряда свободно распространяемых программ для ZX Spectrum и PC, http://www.ivr.da.ru.