Информация о модулях из этой главы
Имя | Идентификатор на CPAN | Версия | |
File : : Find (входит в состав Perl) | |||
File : : Spec (входит в состав Perl) | |||
Cwd (входит в состав Perl) | |||
Win32: :File: :GetAttributes (входит в состав ActiveSta-te Perl) | |||
Win32 : : FileSecu rity (входит в состав ActiveState Perl) | |||
File : : Basename (входит в состав Perl) | |||
Getopt : : Std (входит в состав Perl)
Quota | TOMZO | 1.2.3 | |
срап-тас
(связка) |
CNANDOR | 0.40 | |
Mac: :AppleEvents: : Simple | CNANDOR | 0.81 | |
Mac: :Glue | CNANDOR | 0.58 |
Источники подробной информации
Страницы руководства по perlport - просто неоценимый источник информации для Perl-программистов о различиях платформ. Крис Нандор (Chris Nandor) и Гурасами Сарати (Gurasamy Sarathy) дискутировали по поводу первых версий этого руководства на Perl Conference 2.0; Нандор опубликовал некоторые выдержки из этой беседы на http:// pudge.net/macperl/tpc/98.
Обход файловой системы при помощи модуля File::Find
Теперь, когда вы познакомились с основами просмотра файловой системы, покажем более быстрый и изящный способ сделать то же самое. В Perl есть модуль F; ie: : Fi nd, позволяющий эмулировать команду find из Unix. Самый простой способ начать работать с этим модулем - воспользоваться командой find2perl для
генерирования кода-прототипа Perl.
С flnd2perl не всегда легко работать в Perl в отличных от Unix-системах. Для ее выполнения пользователям MacOS, например, понадобится Macintosh Programmer's Workshop (MPW) либо им придется изменить исходный код, чтобы получать ffARGV в диалоговом окне. Вот пример кода, позаимствованный у Криса Нандора (Chris Nandor), соавтора «MacPerl: Power and Ease» (MacPerl: Сила и легкость):
@ARGV = @ARGV 9 @ARGV :
split '\s". MacPerl: :Ask( "Arguments'7' );
Во всех перенесенных версиях есть модуль File; Fid, который используют команды find2perl и find.pl, так что ото не должно стать проблемой. Позже в этой главе мы покажем, как вызвать его напрямую.
Предположим, что вам нужна программа для поиска файлов beeskneen в каталоге /home. Вот командная строка, использующая команду Unix find:
% find /home -name beesknees -print
Передадим те же параметры команде
find2perl:
% find2perl /home -name beesknees -print и получим:
S'/usr/bin/oerl
eval 'exec /usr/bin/perl -S $0 ${1 + 'W}' if Srunning under some shell;
require "find.pl";
ff Обходим нужные файловые системы
&find('/home'):
exit;
sup wanted {
/"bee^kneesS/ && p--irt("$narrev' ';.
}
Код, сгенерированный командой find2perl, довольно прямолинеен. Он загружает необходимую библиотеку find.pl при помощи оператора require, затем вызывает подпрограмму &гinc() с именем начального каталога. Вскоре мы обсудим назначение подпрограммы &war'terj( ), поскольку именно здесь будут находиться все интересные изменения, которые мы хотим изучить. Перед тем как вносить изменения в этот код, важно обратить внимание на те немногие моменты, которые могут не показаться очевидными при рассмотрении приведенного фрагмента:
Те, кто работал над модулем File: :Find, столкнулись с проблемой переносимости этого модуля на другие платформы. Внутренние подпрограммы модуля File: : Find действуют так, что один и тот же код работает и в Unix, и в MacOS, и в NT, и в VMS и т. д.
Хотя код, сгенерированный find2perl, на первый взгляд похож на код Perl 4 (например, тут используется require для загрузки файла .pi), find.pl в действительности устанавливает несколько псевдонимов из PerlS. Обычно бывает полезно заглянуть «под завесу», прежде чем использовать модуль в собственной программе. Если вам нужен исходный код модуля, уже установленного в системе, то, выполнив команду perl -F или следующую команду, вы получите список каталогов стандартных библиотек:
% perl-e 'print join("\n",@INC,"")'
Давайте поговорим о подпрограмме &wanted(), которую мы изменим для своих нужд. Подпрограмма &wanted() вызывается для каждого найденного &find() (или &File: : Find: :f ind(), чтобы быть точным) файла или каталога при обходе файловой системы. Именно код из &war': j(} должен выбирать «интересные» файлы или каталоги и работать с ними. В примере, приведенном выше, сначала проверяется соответствие имени файла или каталога строке beesknees. Если они совпадают, оператор && заставляет Perl выполнить оператор print, чтобы вывести имя найденного файла.
При создании собственных подпрограмм &wanted() нам придется учитывать два практических момента. Поскольку &w,,nted() вызывается по одному разу для имени каждого файла или каталога, важно сделать этот код коротким и аккуратным. Чем быстрее мы сможем выйти из подпрограммы &warted(), тем быстрее find сможет перейти к новому файлу или каталогу и тем быстрее будет выполняться вся программа. Также важно иметь в виду «закадровые» соображения совместимости, о которых мы недавно упоминали. Было бы позором одновременно иметь переносимый вызов &fina() и системно-зависимую подпрограмму &wanted() (кроме случаев, где этого невозможно избежать). Несколько подсказок, помогающих избежать такой ситуации, можно получить, посмотрев на текст модуля File: : Find.
Для первого использования модуля File:: Find давайте перепишем пример, созданный для удаления core-файлов, и затем немного его расширим. Для начала наберем:
# find2perl -name core -print что даст нам следующее: require "! ind.pi"
&find('. ' ) exit,
sub wanted !
/"coreS/ && DrintC'Sname^i"):
}
Затем мы добавим -s к строке вызова Perl и изменим подпрограмму
&wanted():
sub wanted (
/"core$/ && prin+("$i>ame\n") && dpfired $r && ::nifik($rapf4;
При вызове программы с ключом -г мы получаем необходимую нам функцию (удаление core-файлов). Внесем небольшое изменение, добавляющее некоторую степень защиты нашему потенциально разрушительному коду:
oud wanted {
/"coreS/ && -s $nairie *& print("$name\n") &&
defined $r s& i;nlink($name); }
Теперь, перед тем как выводить имя файла или удалять его, мы проверяем, не является ли размер файла core нулевым. Некоторые пользователи иногда создают ссылку на /dev/null с именем core в своем домашнем каталоге, чтобы core-файлы в нем не сохранялись. Параметр —s указывается для того, чтобы убедиться, что по ошибке не будут уда лены ссылки или файлы нулевой длины. Если мы хотим действовать осторожнее, нам следует выполнить еще две дополнительные проверки:
Открыть файл и убедиться, что этот файл действительно является core-файлом. Сделать это можно как из Perl, так и вызвав команду Unix file. Определить, что файл действительно является core-файлом, может оказаться достаточно сложно в случае, если файловые системы смонтированы по сети с компьютерами другой архитектуры с иными форматами core-файлов.
Посмотреть на время изменения файла. Если кто-то в настоящий момент отлаживает программу, используя файл core, он вряд ли обрадуется, если вы утащите этот файл прямо «из-под нее».
Давайте на время отдохнем от мира Unix и посмотрим на примеры, имеющие отношение к MacOS и Windows NT/2000. Ранее в этой главе я уже говорил, что в MacOS у каждого файла есть два атрибута - созда телъ и тип,
позволяющие операционной системе определить, какое
приложение создало этот файл и какого он типа. Эти атрибуты хранятся в виде четырехсимвольных строк. Например, для текстового документа, созданного приложением SimpleText, эти атрибуты будут иметь значения ttxt (для создателя) и TEXT (для типа). Из Perl (только для MacPerl) мы можем получить эту информацию при помощи функции MacPerl: :GetFileInfo(). Синтаксис ее таков:
$type = MacPerl::GetFileInfo(filename); или:
($creator,$type) = MacPerl::GetFileInfo(filename);
Чтобы найти все текстовые файлы в файловой системе MacOS, мы можем выполнить следующее:
use File::Find;
&File::Find::find(\&wanted,"Macintosh HD:");
sub wanted{ -f $_ && MacPerl;:GetFileInfo($_) eq "TEXT" &&
print "$Find::File::name\n"; }
Вы, должно быть, заметили, что это выглядит немного иначе, чем наши предыдущие примеры. Однако действует этот код точно так же. Мы просто вызываем процедуры из File: :Find напрямую, без find.pl. Кроме того, мы используем переменную $name, определенную в пространстве имен File: :Find, чтобы вывести абсолютный путь файла, а не только его имя. Взгляните на полный список переменных, определяемых File: : Find при обходе файловой системы (табл. 2.2).
Таблица 2.2. Переменные File::Find
Переменная |
Смысл |
$_ |
Имя текущего файла |
$File: :Find: :dir |
Имя текущего каталога |
$File: : Find: : name | Полный путь для текущего файла (т. е. $File: : Find :dir/$_) |
use File::Find: use Win32::File:
&File::Find':find(\&wapted."\\");
sub wanton i -f $.. && ft значение переменной attr присваивается функцией
# Win32::File::GetAttributes (Win32: :File: :GetAttnbutes($_. Sattr)) &&
($at.tr & HIDDEN) &&
print "SFile: : Find: : narreV1",
}
Этот пример ищет по всей файловой системе на текущем диске все скрытые файлы (т. е. те файлы, у которых установлен атрибут HIDDEN). Этот код работает и на NTFS и на FAT.
А вот пример для файловой системы NTFS, который ищет все файлы, если к ним разрешен полный доступ для специальной группы Everyone, и выводит их имена:
use File::Find;
use Win32: : FileSecunty;
tt Определяем маску DACL для полного доступа $fullmask = Win32:
: FileSecunty: :MakeMask(FULL);
&find(\&wanted,"\\");
sub wanted {
fl Win32::FileSecurity::Get не любит файл подкачки и pagefile.sys, пропустить его
next if ($_ eq "pagefile.sys"); (-f $_) &&
Win32: :FileSecunty: :Get($_, \%users) &&
(defined $users{"Everyone"}) &&
($users{"Everyone"} == Sfullmask) &&
print "$File::Find::name\n";
}
В вышеприведенном коде мы запрашиваем все файлы у списка контроля доступа ACL (кроме файла подкачки Windows NT). Затем мы проверяем, есть ли в этом списке запись для группы Everyone. Если есть, мы сравниваем запись Everyone со значением для полного доступа (полученным MakeMask()) и выводим абсолютный путь файла, если они совпадают.
А вот еще один пример из реальной жизни, демонстрирующий, насколько полезным может оказаться даже самый простой код. Недавно я пытался дефрагментировать (заново перестроенный) раздел NT на диске своего портативного компьютера, но все закончилось сообщением об ошибке Metadata Corruption Error (повреждение метаданных). Внимательно изучая веб-сайт производителя программного обеспечения, я нашел там замечание, что «такая ситуация может быть вызвана наличием файлов, длина имен которых превышает допустимую в Windows NT». Там было предложено найти эти файлы, копируя каждый каталог на новое место и сравнивая количество файлов в оригинале л Обход файловой системы при помощи модуля File::Find
копии. Если в копии каталога файлов меньше, необходимо найти те файлы, которые не были скопированы.
С учетом количества каталогов в моем разделе и времени, необходимого для выполнения этой процедуры, такое решение представляется просто нелепым. Вместо этого я написал следующее, используя уже обсужденные методы:
require "find.pl";
ft Обходим нужные файловые системы
&find(\&wanted. '. ')'; print "max:$max\n";
exit;
sub wanted {
return unless -f $_; if (length($_) > $maxlength)f $max = $name;
Smaxlength = length($_); }
if (length($name) > 200) { print $name,"\rT:}
}
В результате будут выведены имена файлов длиной более 200 символов, а также самое длинное найденное имя. Работа сделана, спасибо Perl.
Давайте снова вернемся к Unix, чтобы закончить этот раздел довольно сложным примером. Идея, которая представляется слишком простой в контексте системного администрирования, но в итоге может принести огромную пользу - это понятие наделения пользователя полномочиями. Если ваши пользователи могут решить свои проблемы самостоятельно при помощи средств, которые вы им предоставляете, от этого все только выиграют.
Большая часть этой главы посвящена решению проблем, возникающих при переполнении файловой системы. Зачастую это происходит из-за того, что пользователи недостаточно осведомлены о своем окружении, либо из-за того, что слишком обременительно выполнять операции по управлению дисковым пространством. Множество писем в службу поддержки начинаются со слов «В моем домашнем каталоге больше нет свободного места, но я не знаю из-за чего». Вот скелет сценария
needspace, который может помочь пользователям, столкнувшимся с этой проблемой. Пользователь просто набирает needspace, и сценарий пытается найти в домашнем каталоге пользователя то, что
можно удалить. Он ищет файлы двух типов: резервные копии и те файлы, которые можно автоматически создать заново. Давайте внимательно рассмотрим код:
use File::Find; use File::Bascname;
# массив расшипеннй файлов и расширений, из которых они могут быть получены
% derivations = (" dvi" => '.tex".
".aux" => ".tex",
".toe" => ". tex"
" . 0" =.' C".
}
Мы начнем с того, что загрузим нужные нам библиотеки: знакомый уже модуль File: :Fird и другую полезную библиотеку File: :Взяема;"е. Эта библиотека пригодится при разборе путей файлов. Затем мы инициализируем хэш-таблицу известными расширениями производных файлов; например, мы знаем, что если выполнить команду ТеХ или LaTeX для файла happy.tex, мы можем получить файл happy. Jui, и чти Обход файловой системы при помощи модуля File::Find
happy. можно получить, скорее всего, скомпилировав файл happy.c
компилятором С. Выражение «скорее всего» употреблено потому, что иногда требуется несколько исходных файлов, чтобы сгенерировать один файл. Однако мы можем делать только простые предположения, основываясь на расширениях файлов. Обобщенный анализ зависимостей - сложная задача, и мы даже не будем пытаться решать ее здесь.
Затем мы определяем местонахождение домашнего каталога пользователя, получая идентификатор пользователя, выполняющего сценарий ($<), и передаем его функции getpwu;d(). qetowi,ii;() возвращает информацию из файла паролей в виде списка (подробности об этом позже); индекс массива ([7]) выбирает из этого списка элемент, соответствующий домашнему каталогу. Существуют способы получить эту информацию при помощи данных из командного интерпретатора (например, обратившись к переменной окружения $НОМЕ), но в таком виде код переносится лучше.
Когда не надо использовать модуль File::Find
Когда метод Filt: : Find не подходит? На ум приходят четыре ситуации:
Если файловая система, с которой вы работаете, не следует обычной семантике, вы не сможете применять этот модуль. Например, драйвер файловой системы NTFS для Linux, который я использовал при решении проблемы с упавшим компьютером, почему-то не выводил в пустых каталогах «. » или «..
». Это очень мешало File: : F i nrl.
Если вам нужно изменять имена каталогов во время обхода файловой системы, File: :Finn теряется и начинает вести себя непредсказуемым образом.
Если вам нужно разыменовывать символические ссылки на каталоги (для Unix), Fi le: : Find пропустит их.
Если вам нужно обойти файловую систему, смонтированную на вашей машине (например, файловую систему Unix, смонтированную через NFS на машине с Windows), File: :Find будет использовать семантику, принятую для «родной» файловой системы.
Вряд ли вы столкнетесь с этими ситуациями, но если это произойдет, загляните в раздел этой главы, посвященный обходу файловой системы вручную.
Получив домашний каталог, мы переходим в него и начинаем сканирование, используя вызов &f ind() так же, как и в предыдущих примерах:
$homedir = (getpwuid($<))
# находим домашний катало; пользователе
chdir($hO!!ied! r) or
die "Невозможно войти в хк, домашний каталог Shoinodi':$!";
$1=1; # не буферизованный вывод в STDOUT print "Поиск";
find(\&wanted. "."), # проходим по каталогам, &wanted выполняет
# всю работу
Вот как выглядит вызываемая нами подпрограмма &wanted(). Сначала она ищет core-файлы, а также резервные копии и автосохраненные файлы, остающиеся после редактирования в emacs. Мы считаем, что эти файлы можно удалить, не проверяя существование исходных файлов (вероятно, это небезопасное предположение). Если такие файлы найдены, их размеры и пути к ним сохраняются в хэше, ключами которого являются пути к файлам, а значениями - размеры этих файлов.
В остальной части подпрограммы подобным образом отыскиваются производные файлы. Мы вызываем подпрограмму uBascFiiotx, для того чтобы убедиться, что эти файлы можно получить из других файлов этого же каталога. Если подпрограмма возвращает значение «истина», мы сохраняем имя файла и его размер для последующего использования:
sub wanted {
ищем core-файлы, сохраняем их и возвращаемся $_ eq "core" &&
($core{$File ::Find :: n came} = (siai( })[7]) && return;
# ищем резервные копии и автосохраненье после редактирования файла
($emacs{$File::Find::name} = (stat(_) ))[?]) &&
return;
&& ($tex{$File: :Find: :name} = (stat) && return;
# ищем производные файлов
&BaseFileExists($File::Find::name) &&
($doto{$File::Find::name} = (stat(__))[7J) && return;
Вот текст подпрограммы, проверяющей,
можно ли получить данный файл из другого файла в этом же каталоге
(например, существует ли файл happy, если мы нашли файл );
sub BaseFileExists {
my($name,$path,Ssuffix) = &File::Basename::fileparse($_[0], '\. . *' );
# если мы не знаем, как получить файл этого типа return
unless (defined Sderivations-1 ;{$suffix});
# все просто, мы видели исходный файл раньше return 1 if (defined
$baseseen{$path. $riame. $der rivations{$suf fix}});
# если файл ( или ссылка на файл) существует и имеет ненулевой размер
return 1 if (-s $name $derivations{$su '.,ffix} &&
++$baseseen
{
Вот как выполняется эта подпрограмма:
&File: : Basename: : fileparse() используется для выделения из пути имени файла, пути к файлу и его суг)ффикса (например resume.dvi, /home/cindy/docs/, .dui).
Затем суффикс файла проверяется, чтобы определить, считаем мы этот файл производным или нет. Если нет, мы возвращаем значение 0 (т. е. «ложь» в скалярном контексте).
Затем мы проверяем, встречался ли нам файл, исходный (base file) по отношению к данному, и если да, то возвращаем значение «истина». В некоторых ситуациях (в частности, в случае с TeX/LaTeX), из одного исходного файла можно получить несколько производных. Такая проверка ускоряет выполнение сценария, т. к. мы в этом случае избавлены от обхода файловой системы.
Если мы не встречали раньше исходный файл (с тем же именем, но другим расширением), то проверяем, существует ли он и больше ли нуля его размер. Если да, мы сохраняем информацию о файле и возвращаем 1 (т. е. «истина» в скалярном контексте).
Теперь нам остается только вывести информацию, которую мы собрали при обходе файловой системы:
foreach my $path (keys %core){
print "Найден core-файл, занимающий -1.&BytesToMeg($core{$path}).
"MB в ",&File::Basename::dirname($path).".\n": }
if (keys %emacs){
"Следующие файлы, скорее всего, являются резервными копиями, созданными emacs:\n";
# изменяем путь, чтобы
# вывод был аккуратнее print "$path ($emacs{$path} байт)\"
}
print "\пОни занимают ".&BytesToMeg($tempsize)."MB в сумме."; $tempsize=0; }
if (keys %tex){
print "Следующие файлы, скорее всего, можно получить заново, если
# вывод был аккуратнее print "$path ($tex{$path} байт)":
}
print ЛпОни занимают ".&BytesToMeg(Stempsize),"MB в сумме.\п": $tenipsize=0: }
if (keys %doto)! print
" Следующие файлы, скорее, всего, можно получить, если вновь
$path =" s/~$homedir/"/; tf изменяем путь, чтобы
tt вывод был аккуратнее print "$path ($doto{$path} байт)";
}
print "\Они занимают ".&BytesToMeg($tempsize)."MB в сумме."; $tempsize=0; }
sub BytesToMegl # преобразуем размер в байтах в формат ХХХМВ
return sprintfCl%.2f,($_[0]/1024000)); }
Прежде чем закончить этот разговор, надо заметить, что предыдущий пример можно расширять множеством способов. Пределов для программ такого типа просто не существует. Вот несколько идей:
Используйте более сложные структуры данных для хранения расширений производных файлов и найденных файлов. Приведенный выше код был написан с расчетом на то, чтобы его было легко читать людям, не очень хорошо разбирающимся со структурами данных в Perl. В нем используются повторяющиеся фрагменты и его довольно тяжело расширить, если в этом появится необходимость. В идеале было бы неплохо, чтобы все расширения производных файлов не были связаны со специальными хэшами (например %tex) в коде.
Ищите каталоги, в которых веб-броузеры кэшируют страницы (это очень распространенный источник потерянного пространства на диске).
Предложите пользователю удалить найденные файлы. Для удаления файлов используйте оператор unlink() и подпрограмму rmpath из модуля File: :Path.
Больше анализируйте файлы, вместо того чтобы строить предположения по их именам.
Perl приходит на помощь
Лэптопы падают медленно. По крайней мере, это выглядело именно так, когда компьютер, с помощью которого я писал эту книгу, упал со стола на твердый деревянный пол. Когда я его поднял, он по-прежнему был цел и работал. Но в процессе проверки лэптопа на предмет наличия повреждений я заметил, что он начал работать все медленнее и медленнее. Мало того, время от времени он стал устрашающе гудеть и жужжать во время обращения к диску. Решив, что замедление было вызвано программной ошибкой, я выключил компьютер. Но выключаться корректно он отказывался. Это был плохой знак.
Еще хуже было нежелание компьютера загружаться. Он начинал процесс загрузки Windows NT, но затем выдавал ошибку «файл не найден» и замирал. Теперь стало ясно, что падение вызвало серьезное физическое повреждение жесткого диска. Вероятно, головки проскользнули по поверхности диска, уничтожив файлы и каталоги, находившиеся в открытом состоянии. Передо мной встал вопрос: «Остались ли неповрежденными хоть какие-то файлы? Выжили ли файлы этой книги!»
Первым делом я попытался загрузить Linux, другую операционную систему, установленную на портативном компьютере. Linux загрузился нормально; это меня подбодрило. Однако файлы этой книги находились в разделе NTFS, который не загружался. Используя драйвер NTFS для Linux, написанный Мартином фон Левисом (Martin von Lowis) и доступный на http://www.informatlk.hu-berlin.de/loewis/ntfs/ (теперь он поставляется вместе с ядрами Linux 2.2), я смонтировал раздел и обрадовался, увидев, что все мои файлы казались неповрежденными.
Мои попытки скопировать файлы из этого раздела проходили успешно, но лишь до тех пор, пока я не достиг некоторого файла. Диск опять издал этот зловещий звук, и скопировать файл не удалось. Было ясно. что если я хочу спасти свои данные, мне нужно пропустить все поврежденные файлы на диске. Программа, которой я пользовался (gnutar), могла пропустить список файлов. Вопрос был только в том, какие файлы следовало пропускать? Когда возникла эта неприятность, в файловой системе было больше шестнадцати тысяч файлов. Как я мог разобраться, какие файлы были повреждены, а какие нет? Запускать gnutar
снова и снова было отнюдь не лучшей стратегией. С такой задачей мог справиться Perl!
Позже в этой главе я покажу исходный код, которым пользовался для решения возникшей проблемы. Чтобы понять, как работает этот код, нам следует сначала познакомиться с файловыми системами вообще и, в частности, с тем, как мы работаем с ними в Perl.
Получение сведений об использовании файловой системы
Располагая методами контроля над использованном файловой системы, которые мы только что рассмотрели, вполне естественно проверить, как они работают. Чтобы закончить эту главу, давайте рассмотрим способ получения сведений об использовании файловой системы для каждой из операционных систем.
MacOS - операционная система, для которой эта задача наиболее сложна. В MacOS есть процедура PBHGel из Macintosh Toolbox для получения информации о томах, но в настоящее время не существует модулей MacPerl, которые сделали бы простым вызов этой функции. Вместо этого мы используем обходной путь и поручим Finder запросить эту информацию для нас. Благодаря модулю связки это легко осуществить, но из-за необходимости предварительных действий в MacOS эта задача выполняется сложнее всего.
Все используемые далее материалы основаны на работе Криса Нандора (Chris Nandor) и их можно найти на http://pudge.net или на CPAN. Наберитесь терпения, пока мы будем рассматривать процесс настройки шаг за шагом:
Установите связку срап-тас. В срап-тас входит модуль CPAN, ргс, написанный Андреасом Кенигом (Andreas J. Konig), и другие нужные методы, о которых говорилось в главе 1. Даже если вы не хотите получать сведения об использовании файловой системы в MacOS, эту связку лучше установить. После того как вы ее установите, обязательно выполните все инструкции из файла
README.
Установите последнюю версию модуля Mac: :AppleEvents: :Simple, перетащив файл дистрибутива в installme.
Установите модуль Mac::Glue. «Крошка» installme распаковывает содержимое дистрибутива Mac : :Gluc в новый каталог в процессе установки. Обязательно запустите сценарии установки gluedialect и glue script adds из подкаталога scripts того каталога, в который распакован дистрибутив.
Создайте файл связки для Finder. Откройте System Folder и перетащите файл Finder на вершину gluemac, чтобы создать необходимый файл (и, что очень приятно, документацию для него).
Этот сложный процесс установки позволяет нам написать такую простенькую на вид программу:
use Mac::61ue qw(:all);
$fobj = newMac::61ue 'Finder';
Svolumename = "Macintosh HD"; ft имя одного из смонтированных дисков.
Stotal = $fobj->get($fobj->prop('capacity', disk => Svolumename), as => 'doub');
$free = $fobj->get($fobj->prop('free_space', disk => Svolumename), as => 'doub');
print "свободно $free байт из $total\n";
Перейдем к более простым темам. Для того чтобы получить ту же информацию на компьютере с Win32, мы могли бы использовать модуль Win32: : AdminMisc, написанный Дэйвом Ротом (Dave Roth):
use Win32::AdminMisc;
($total,$free) = Win32::AdminMisc::GetDriveSpace("c:\\");
print "свободно $free байт из $total\n";
И, наконец, закончим эту главу рассмотрением эквивалентного варианта для Unix. Существует несколько модулей для Unix, включая Fi-lesys: :DiskSpace Фабьена Тассена (Fabien Tassin), Filesys: :Df Яна Гафри (Ian Guthrie) и Filesys: :DiskFree Алана Баркли (Alan R. Barclay). В первых двух из них используется системный вызов statvf s(), а последний анализирует вывод Unix-команды df на всех поддерживаемых системах. Выбор какого-либо из этих модулей зависит от ваших предпочтений и от того, что поддерживается вашей операционной системой. Я предпочитаю Filesys: :Df, поскольку он предлагает массу возможностей и не запускает другой процесс (потенциальный риск, как уже говорилось в главе 1) во время запроса. Вот один из способов написания эквивалентного двум предыдущим примерам варианта:
use Filesys::Df; $fobj = df("/");
print $fobj->{su_bavail}*1024." байт в ".
$fobj->{su_blocks}*1024." байт свободно\n";
Нам необходимо выполнить некоторые расчеты (а именно: *1024), поскольку Filesys: : Df возвращает значения в блоках, а каждый блок равен 1024 байтам в нашей системе. Функции df () этого модуля может быть передан второй необязательный параметр, определяющий размер блока, если это необходимо. Следует также отметить в этом коде два запрошенных значения хэша. su_bavail и su_blocks - это возвращенные модулем значения размера диска и объема использованного пространства. В большинстве файловых систем Unix команда df выводит информацию об использованном пространстве, скрывая 10% диска, зарезервированных для суперпользователя. При желании узнать размер доступного и свободного в настоящий момент дискового пространства с точки зрения обычного пользователя мы должны использовать user_blocks и user_bavail.
Мы только что видели ключевые фрагменты кода, при помощи которых можно создавать более сложные системы, наблюдающие и управляющие пространством на дисках. Эти наблюдатели за файловыми системами помогут вам решить проблемы с пространством на дисках еще до их появления.
Прогулка по файловой системе
Наверняка вам уже не терпится посмотреть на какие-нибудь приложения, написанные на Perl. Начнем мы с «прогулки по файловой системе» - одной из наиболее часто встречающихся задач системного администрирования, связанных с файловыми системами. Обычно этот процесс состоит в поиске по всем деревьям каталогов и выполнении некоторого действия в зависимости от результатов поиска. Для этой задачи в каждой операционной системе есть свое средство. В Unix это команда find, в Windows NT/2000 - Find Files or Folders или
Search For Files or Folders, а в MacOS - Find File или Sherlock. Все эти команды полезны для поиска, но выполнять произвольные сложные действия, по мере нахождения требуемых файлов, они не способны. Мы увидим, каким образом Perl позволяет писать более изысканные обзорные программы, начав с простейших и наращивая сложность по мере продвижения.
Для начала давайте рассмотрим простой сценарий, четко ставящий перед нами задачу, которую необходимо решить. В этом сценарии мы предстанем системным администратором Unix с переполненной файловой системой пользователей и исчерпанным дисковым пространством. (Мы начали с Unix, но вскоре рассмотрим и другие операционные системы.)
Мы не можем добавить новое дисковое пространство, не имея на это денег, поэтому попытаемся рациональнее использовать существующие ресурсы. Первый наш шаг - удалить из файловой системы все файлы, которые можно удалить. В Unix первые кандидаты на удаление - это core-файлы, которые остаются после аварийных завершений программ. Большинство пользователей либо не замечают, что эти файлы создаются, либо просто игнорируют их, оставляя напрасно занятыми большие дисковые пространства. Нам нужно средство для поиска и удаления таких «пустышек» из файловой системы.
Обзор файловой системы мы начинаем с чтения содержимого какого-либо каталога и затем продолжаем обход с этой точки. Давайте немного упростим задачу и начнем с программы, которая изучает содержимое текущего каталога и сообщает, были ли найдены в нем core-файлы и другие каталоги-кандидаты для поиска.
Начнем мы с того, что откроем каталог, используя примерно тот же синтаксис, что и для открытия файла. Если попытка была неудачной, мы выходим из программы и выводим сообщение об ошибке ($! ), установленное вызовом opendir( ):
opendir(DIF), ". ") or die "He могу открыть текущий каталог: $'\л';
Мы получаем дескриптор каталога, в нашем случае DIR, который можно передать функции readdir( ), чтобы получить список файлов и каталогов в текущем каталоге. Если readdir() не может прочитать содержимое каталога, выводится сообщение об ошибке (которое объясняет, почему попытка была неудачной) и программа завершает работу:
# читаем имена файлов/каталогов данного каталога в @names
@names = readdir(DIR) or die "Невозможно прочитать текущий каталог:$! \n";
Затем закрываем открытый дескриптор каталога:
closedir(DIR);
Теперь можно работать с полученными именами:
foreach $name (@names) {
next if ($name eq "."); # пропускаем текущий каталог
next if ($name eq ".."); # пропускаем родительский каталог
if (-d $name){ # является ли каталогом?
print "найден каталог: $name\n"; next: # можно перейти к следующему имени
# из цикла for
}
if ($name eq "core") { # это файл с именем core?
print "найден! \n";
}
}
Теперь у нас есть очень простая программа, которая проверяет содержимое одного каталога. Она не только не обходит файловую систему, но даже не «проползает» по ней. Чтобы пройти по файловой системе, мы должны зайти во все каталоги, которые нам встретятся, и посмотреть на их содержимое. Если в этих подкаталогах есть еще подкаталоги, в них мы тоже должны заглянуть.
Когда у вас есть иерархия контейнеров и операция, которая одинаково выполняется на каждом внешнем контейнере и каждом внутреннем контейнере из этой иерархии, это называется рекурсией (по крайней мере в компьютерных науках). До тех пор пока иерархия не очень глубока и не зацикливается (т. е. все контейнеры содержат только непосредственно дочерние объекты и в них отсутствуют ссылки на другие части иерархии), рекурсивные решения имеют наибольший смысл.
Именно с этим мы сталкиваемся и в нашем примере: мы собираемся просмотреть каталог, все его подкаталоги, все их подкаталоги и т. д.
Если вы никогда раньше не видели рекурсивного кода (т. е. кода, который вызывает себя), на первый взгляд он вам может показаться несколько странным. Рекурсивный код чем-то напоминает процесс раскраски матрешек. Там тоже внутри одной куклы находится другая кукла такой же формы, но меньшего размера, внутри которой есть еще одна кукла, и т. д. до самой маленькой куклы в центре.
Инструкция по раскраске матрешек могла бы выглядеть так:
Изучите куклу, которая находится перед вами. Внутри у нее есть кукла меньшего размера? Если да, то вытащите ее оттуда.
Повторяйте шаг 1 с вытащенными куклами до тех пор, пока не дойдете до центра.
Раскрасьте центральную куклу. Когда она высохнет, поместите ее во внешнюю по отношению к ней куклу и повторите шаг 3 со следующим контейнером.
Этот процесс одинаков на каждом шаге. Если у предмета, который вы держите в руках, есть «подпредметы», отложите предмет в сторону и займитесь сначала «подпредметом». Если же подпредметов у него нет, произведите с ним необходимые действия и займитесь последним отложенным предметом.
В терминах программирования это обычно подпрограмма, которая работает с контейнерами. Сначала в ней проверяется наличие подконтейнеров у текущего контейнера. Если они есть, подпрограмма вызывает себя, чтобы выполнить какие-либо действия с подконтейнером. Если же их нет, выполняется некое действие и выполняется возврат туда, откуда подпрограмма вызывалась. Если вы не видели код, который вызывает сам себя, я советую посидеть с карандашом и бумагой и проследить выполнение программы до тех пор, пока вы не убедитесь, что она действительно работает.
Давайте рассмотрим примеры кода с рекурсией. Чтобы добавить рекурсию в наш код, мы сначала перенесем операцию сканирования каталога и действия над его содержимым в отдельную подпрограмму ScanDirect.ory(). Подпрограмма ScanDi'tctoryf) принимает единственный аргумент - каталог, который надо просканировать. Она просматривает текущий каталог, входит в нужный подкаталог и сканирует его. По завершении сканирования подпрограмма возвращается в каталог. из которого она была вызвана. Вот новый вариант программы:
# s также считается устаревшим, многие программисты
# предпочитают использовать отдельный модуль
ft (из семейства модулейОет.ор1: : ) для разбора (анализа) параметров
use Cwd: n модуль для определения текущего рабочего каталога
# Эта подпрограмма принимает имя каталога и рекурсивно
# сканирует файловую систему, начиная с этого места, ищет и файлы с именем "core"
sub ScanDirectory{
my (Sworkdir) = shift;
my (Sstartdir) = &cwd; ft запомнить, откуда мы начали
cndir(Jworkdir) or die "Невозможно войти в каталог $workdir:$!\n";
opendir(DIR, ".") or die "Невозможно открыть Sworkdir:$!\n";
my fflnames = readdir(DIR) or die "Невозможно прочитать Sworkdir:$!\n";
closedir(DIR);
foreach my $name (@names){ next if (Sname eq "."); next if ($name eq "..");
if (-1 $name){ # пропускаем ссылки
next; }
if (-d $name){ f* это каталог7
&ScanDirectory($name); next; }
if (Sname eq "core") { ft имя файла "core"? и
если в командной строке указан ключ, на самом и деле удаляем этот файл
if (defined $r) {
unlink($name) or die "Невозможно удалить Sname:$!\n";
}
else {
print "найден в Sworkdir1\n": > } } chdir(Sstartdir) or
die "Невозможно перейти к каталогу $startdir:$[\n"; }
&ScanDirectcry(".");
Самое важное отличие от предыдущего примера заключается в изменении поведения программы в случае, если найден подкаталог внутри требуемого каталога. Теперь, если был найден каталог, вместо того
чтобы сообщать об этом, как было в предыдущем примере, наша программа рекурсивно вызывает себя, чтобы изучить сначала содержимое этого каталога. По окончании сканирования всего подкаталога (т. е. вызов ScanDirectory() возвращает значение) программа возвращается к просмотру остального содержимого текущего каталога.
Для того чтобы сделать нашу программу полнофункциональным ликвидатором core-файлов, мы добавили в нее функцию удаления файлов. Обратите внимание на то, как это реализовано: файлы будут удалены, только если сценарий вызывается с определенным ключом -s (от «remove» - удаление) в командной строке.
В Perl мы указываем встроенный ключ -s в строке вызова (#! /us г/с in, perl -s) для автоматического разбора параметров. Это самый простой способ разбора параметров, переданных в командной строке. Искушения ради, мы могли бы использовать какой-либо модуль из семейства Getopt. Если в командной строке присутствует ключ (например -г), то при запуске сценария устанавливается глобальная скалярная переменная с тем же именем (например $г). Если Perl вызывается без ключа -г, мы вернемся к старому поведению подпрограммы - она будет лишь сообщать, что найдены core-файлы.
Когда вы пишете автоматические утилиты, постарайтесь сделать так, чтобы разрушительные действия были затруднены. Учтите: Perl, как и большинство серьезных языков программирования, позволяет уничтожить файловую систему без особых усилий.
Теперь, чтобы ориентированные на NT/2000 читатели не подумали, что предыдущие примеры к ним не относятся, покажем, что эта программа может пригодиться и для них. Единственное изменение строки:
if (Sname eq "core") {
на:
if (Sname eq MSCREATE.DIR") {
позволяет создать программу, которая удалит все раздражающие скрытые файлы нулевой длины, забытые инсталляторами некоторых программ Microsoft.
Имея в запасе этот код, давайте вернемся к проблеме, с которой начиналась эта глава. После того как мой портативный компьютер приземлился на пол, средство, которое позволило бы определить, какие файлы можно прочитать с диска, а какие нет, стало необходимо мне, как воздух.
Вот какую программу я написал для этого:
use Cwd: # модуль для определения текущего рабочего каталога
$1=1; # отключаем буферизацию ввода/вывода
sub ScanDirectory {
my ($workair) = shift;
my($startdir) = &cwd;
ft запоминаем, откуда мы начали chdir($workdir)
or die "Невозможно зайти а каталог $workdir:$'\n":
opendir(DIR, ".")
or die "Невозможно открыть каталог $workdir:$!\n";
my @names = readdir(DIR);
closedir(DIR);
foreach my $name (@names){ next if ($name eq "."); next if ($name eq "..");
if (-d $name){ ft это каталог?
&ScanDirectory($name); next; } unless (&CheckFile($name)){
print &cwd."/".$name."\n"; # выводим имя
ft поврежденного файла } }
sub CheckFile<
my($name) = shift;
print STDERR "Проверяется ". &cwd."/'".
$name. "\n"; # пытаемся получить состояние файла my @stat = stat($name);
if (!$stat[4] && '$stat[5] && !$stat[6] && !$stat[7] && l$stat[8]){ return 0;
i )
# пытаемся открыть этот файл unless (open(T,"$name")){
return 0: }
tt читаем файл по байту за один раз for (my $i=0;$i< $stat[7]:$i++){
фу $r=sys'-ead(T.$i, 1);
if ($r '= 1) { dose(T); retu--n 0 } >
close(T);
return 1: >
&ScanDirecto-y("."):
Различия между этой программой и последним примером заключаются в наличии дополнительной подпрограммы для проверки каждого найденного файла. Для каждого файла мы вызываем функцию stat, чтобы проверить, можно ли прочитать информацию о файле (например, его размер). Если мы сделать этого не можем, значит, файл поврежден. Если же прочитать эту информацию можно, мы предпринимаем попытку открыть файл. А в качестве последней проверки пытаемся прочитать каждый байт из файла. Это не гарантирует, что файл не поврежден (его содержимое могло измениться), но это говорит о том, что файл можно прочитать.
Вы можете удивиться, зачем применять такую странную функцию, как sysread(), для чтения файла, если можно применить о или г ead (), обычно используемые для этого в Perl. Дело в том, что sysread() позволяет читать файл побайтно, не применяя обычную буферизацию. Если файл поврежден в позиции X, нет смысла ждать, пока будут прочитаны байты в позициях Х+1, Х+2, Х+3 и т. д., как это бывает при обращении к обычным библиотечным функциям. Мы хотим, чтобы попытки читать файл в таком случае прекратились немедленно. Обычно файл читается по кускам в целях повышения производительности, но это нежелательно, т. к. в нашем случае компьютер будет издавать ужасающие звуки в течение длительного времени, когда наткнется на поврежденный файл.
Теперь, после рассмотрения использованной мною программы, я расскажу, чем закончилась эта история. После того как рассмотренный сценарий проработал всю ночь (без преувеличений), он нашел 95 поврежденных файлов из 16000. К счастью, ни один из них не был файлом из книги, которую вы сейчас читаете; я снял копии со всех хороших файлов и перенес их в другое место. Perl просто спас положение.
Работа с дисковыми квотами
Perl-сценарии, подобные приведенным в предыдущем разделе, предлагают нам способы, позволяющие манипулировать ненужными файлами, обилие которых приводит к переполнению диска. Но даже если такие сценарии запускать регулярно, наши действия все равно будут ответными, т. к. администратор уделяет таким файлам время только тогда, когда они уже появились и захламили файловую систему.
Существует другой, более активный подход: квоты на файловые системы. Квоты, или ограничения операционной системы, позволяют ограничить объем дискового пространства, отведенный определенному пользователю. Квоты существуют в Windows 2000 и во всех современных разновидностях Unix. В NT4 для этого необходимы продукты сторонних разработчиков, а в MacOS для пользователей существует понятие S.O.L. (Simply or Sore Out of Luck - просто не повезло).
И хотя это активный подход, поддерживать его гораздо сложнее, чем «чистящие» сценарии, поскольку он применяется ко всем файлам, а не только к лишним, например, к core-файлам. Большинство системных администраторов считают лучшей стратегией использовать комбинацию автоматических «чистящих» сценариев и дисковых квот. Первое помогает ограничить использование второго.
В этом разделе мы поговорим о работе с дисковыми квотами в Unix средствами Perl. Перед тем как перейти к конкретному разговору, нужно понять, как квоты устанавливаются и как их можно ввести вручную. Чтобы сделать возможным применение квот в файловой системе, системный администратор Unix обычно добавляет запись в таблицу смонтированных файловых систем (например файл /etc/fstab или /etc/ufstab) и перезагружает систему либо вручную вызывает команду, разрешающую использование квот (обычно quotaon).
Вот пример файла /etc/vfstab из Solaris:
{(device device mount FS fsck nou'it
#to mount to fsck point 'ype \.<Uijot op
Параметр rq в последнем столбце включает квоты для файловой системы. Хранятся они для каждого пользователя отдельно. Для просмотра информации о квотах пользователя на всех смонтированных файловых системах, на которых квоты применяются, надо вызвать команду quota:
$ quota - v sabrams чтобы получить данные, подобные этим:
Disk quotas for sabrams (u.d 670)
Files'/stem u:apc:
/hoTie/useri 228731 2bOOGO 253000 0 0 :}
В следующих нескольких примерах нас будут интересовать только первые три колонки этого вывода. Первое число - это объем занятого в настоящий момент дискового пространства пользователем sabrams на файловой системе, смонтированной как /home/users. Второе - это размер «мягкой квоты» пользователя. Мягкая квота - это объем дискового пространства, после превышения которого операционная система в течение некоторого времени выдает предупреждения, но не ограничивает выделение дискового пространства. Последнее число - это «жесткая квота», т. е. абсолютный верхний предел для объема пространства, занятого данным пользователем. Если программа попытается использовать еще некоторое дисковое пространство после превышения пользователем квоты, операционная система отвергнет этот запрос и вернет сообщение об ошибке, подобное disk quota excoedud.
При желании изменить размеры квот вручную необходимо использовать команду
edquota, которая загружает небольшой временный файл с информацией о текущих размерах квот в редактор, определяемый переменной окружения EDITOR командного интерпретатора. Вот пример такого файла с информацией об ограничениях для четырех файловых систем, на которых применяются квоты. Скорее всего, домашний каталог этого пользователя находится в каталоге /exprt/server2, т. к. только в этой файловой системе для него отведены квоты:
fs /exprt/serverl blocks (soft = 0, hard = 0) inodes (soft = 0, hard = 0)
fs /exprt/server2 blocks (soft = 250000, hard = 253000) inodes (soft = 0 hard = 0)
fs /exprt/serverS blocks (soft = 0. hard = 0) modes (soft = 0, hard = 0)
fs /exprt/server4 blocks (soft = 0, hard - 0) modes (soft = 0, hard = 0)
Использование edquota вручную может быть удобным способом редактирования ограничений одного пользователя, но это совершенно невозможно в случае десятков, сотен и тысяч учетных записей пользователей. Один из недостатков Unix - нехватка утилит командной строки для редактирования информации о квотах. В большинстве версий Unix есть функции библиотеки С для выполнения этой задачи, но нет утилит командной строки для написания сценариев. И, следуя девизу Perl, что «Существует более одного способа сделать это» («There's More Than One Way To Do It», TMTOWTDI, произносится как «тим-то-ади», «tim-toady»), мы рассмотрим два различных способа установки квот из Perl.
Редактирование квот при помощи edquota
Первый способ требует некоторой хитрости с нашей стороны. Совсем недавно мы упоминали процесс ручной установки дисковых квот: edquota вызывает редактор, в котором пользователь редактирует небольшой текстовый файл, после чего эти изменения используются для обновления информации о квотах. Не существует никаких указаний на то, какие данные необходимо вводить, чтобы внести изменения. В действительности, не существует даже ограничений на то, какой редактор будет применяться. Все, что нужно команде edquota, это программа, которую можно запустить и которая необходимым образом изменит маленький текстовый файл. Подойдет любой допустимый путь (заданный переменной окружения ;TI IOR) к такой программе. Почему бы не указать программе edquota сценарий на Perl? Давайте и рассмотрим такой сценарий в нашем следующем примере.
Наш сценарий должен будет выполнять две задачи: во-первых, он должен принимать от пользователя аргументы командной строки, устанавливать соответствующим образом переменную окружения EDITOR и вызывать программу
edquota. В свою очередь, edquota запустит другую копию нашей программы, которая и займется собственно редактированием этого временного файла. Ниже показана схема действий (рис. 2.1).
Второй копии программы необходимо сообщить, что именно она должна изменить по требованию исходной программы. Как она получает эту информацию из первой копии, вызвавшей edquota, менее очевидно, чем этого бы хотелось. В странице руководства по edquota сказано: «Вызывается редактор vi(l), если только в переменной EDITOR не указано иное». Идея передать аргументы командной строки через EDITOR или другую переменную окружения довольно опасна хотя бы потому, что мы не знаем, как на это отреагирует утилита edquota. Поэтому нам придется полагаться на один из методов межпроцессного взаимодействия, доступных в Perl. Например, два процесса могут:
Передавать друг другу временный файл
Создать именованный канал и общаться по нему
Передавать AppleEvents (в MacOS)
Использовать объект синхронизации (inutex) или оговоренные ключи реестра (в NT/2000)
Общаться через сокеты
Использовать разделяемую память
И так далее. От вас как от программиста зависит, какой метод вы выберете, хотя зачастую определять выбор будут данные. Рассматривая их, вы будете принимать во внимание:
Направление соединения (одно- или двунаправленное?)
Частоту соединения (будет передано одно сообщение или несколько кусочков?)
Размер данных (будет это 10-мегабайтный файл или 20 символов?)
Формат данных (будет это двоичный файл или просто текст фиксированной ширины, разделенный определенным символом?)
Наконец, учитывайте то, насколько сложным вы хотите сделать свой сценарий.
В нашем случае мы собираемся выбрать простой, но мощный метод о о мена информацией. Так как первый процесс должен передать второму только набор инструкций по изменению информации (какие квоты изменять и на какие значения), мы установим между ними стандартный канал Unix. Первый процесс пошлет запрос на изменение в поток вывода, а копия, запущенная программой edquota, прочитает эту информацию со своего потока ввода.
Давайте напишем программу. Первое, что должна делать программа при запуске - это решить, какую роль она должна выполнять. Пусть при первом вызове она получает несколько аргументов командной строки (то, что надо изменить), тогда как во второй раз, уже вызванная программой edquota, она получает только один аргумент (имя временного файла). Программа требует наличия нескольких ключей командной строки, если вызывается более чем с одним аргументом, поэтому мы можем полагаться на данное допущение при выборе роли для нашего сценария. Вот как выглядит код, определяющий роль сценария:
$edquota = "/usr/etc/edquota"; и путь к edquota
Sautoedq = "/usr/adm/autoedquota"; ц полный путь к этому сценарию
ft это первый или второй запуск?
ft если присутствует более одного аргумента - это первый запуск
if ($»ARGV > 0) {
&ParseArgs;
&CallEdquota; }
и в противном случае это второй запуск, и мы должны выполнить редактирование
else {
&EdOuota(); }
Рассмотрим код, вызываемый при первом запуске и используемый для анализа аргументов и вызова edquota через канал:
sub ParseArgsf
use Getopt: :Std;
# для обработки параметров
# Устанавливаем переменную $opt_u равной идентификатору и пользователя,
$opt_f - равной имени файловой системы,
$opt_s - в значение для мягкого ограничения и
$opt_h -К в значение для жесткого ограничения getopt("u:f:s:h:");
двоеточие говорит о том, что у этого
# ключа есть аргумент die "ИСПОЛЬЗОВАНИЕ:
$0 -u uid -f <fsystem> -s <softq> -h <nardq>\n"
if (<$opt_u || !$opt_f || !$opt_s || !$opt_n); }
sub CallEdquotaf
$ENV{"EDITOR"} = Sautoedq;
записываем в
# переменную окружения EDITOR путь к нашему сценарию
operKEPROCFSS. "|$edquota $opt_u") or die
"Невозможно запустить edquota :$! \r,":
посылаем измененные строки во вторую копию сценария
print EPROCESS "$opt_f|$opt^s|$opt_.h\n";
close(EPROCESS); }
Вот вторая часть выполняемого действия:
sub EdOuota {
Stfile = $ARGV[0];
получаем имя временного файла edquota
open(TEMPFILE, Stfile) or die "Невозможно открыть временный файл
# открываем файл-черновик, можно было бы и использовать и
new_tmpfile() open(NEWTEMP, ">$tfile.$$") or die "
Невозможно открыть временный файл-черновик Stfile.$$:$!\";
# получаем строку ввода из первого вызова и отсекаем символ \
chomp($change = <STDIN>);
my($fs,$soft,Shard) = split(/\|/,$change);
разбираем ответ
считываем из временного файла строку. Если она содержит
информацию о файловой системе, которую мы хотим
изменить, изменяем эти значения. Записываем строку
(вероятно, измененную) в черновик, while (<TEMPFILE>){
перезаписываем временный файл измененным черновиком,
так что изменения передаются edquota rename("Stfile.$$",Stfile)
or die "Невозможно переименовать
$tfile.$$ в $t*ile:$!\n":
}
Приведенная выше программа - это всего лишь скелет, но он все же описывает способ автоматического изменения квот. Если вы когда-то пытались изменять вручную квоты для многих пользователей, приведенный выше пример должен вас порадовать. Перед тем как использовать тот сценарий для внесения реальных изменений, необходимо добавить механизм, защищающий от внесения противоречащих друг другу изменений, а также механизм проверки ошибок. В любом случае, технология такого рода может вам пригодиться и в других ситуациях, помимо работы с квотами.
Редактирование квот при помощи модуля Quota
Когда-то очень давно предыдущий метод (или, если быть честным, предыдущий «хак») был единственным способом автоматизировать изменения квот, если, конечно, вас не радовала перспектива редактирования системных вызовов из библиотеки С, чтобы встроить их в интерпретатор Perl. Теперь, когда механизм расширений Perl существенно упростил встраивание библиотечных вызовов в Perl, создание модуля Quota для Perl стало только делом времени. Благодаря Тому Зорнеру (Tom Zoerner) и другим процесс установки квот средствами Perl теперь намного проще, если этот модуль поддерживает вашу версию Unix. Если нет, предыдущий метод все равно будет работать нормально.
Вот небольшой пример, в котором принимаются те же аргументы, что и в предыдущем:
use Getopt::Std;
use Quota:; '
getopt("u:f:s:h:");
die "USAGE: $0 -u uid -f <filesystem> -s <softquota> -h <hard quota>\n"
if (!$opt_u || !$opt_f || ISopt^s || !$opt_h);
$dev = Quota::getcarg($opt_f) or die "Невозможно преобразовать путь
$0ptc;f:$!\n";
($curblock,$soft,Shard,Sbtimeout,Scurinode,$isoft,Sihard,$itimeout)=
Quota::query($dev,$uid) or die "Невозможно запросить квоту для
$uid:$!\n";
Quota::setqlim($dev,$opt_u,$opt_s,$opt_h,$isoft,$ihard,1) or
die " Невозможно установить квоту:$!\n";
После анализа аргументов остаются три простых шага: во-первых, мы используем Quota: :getcarg() для получения идентификатора устройства, который передается другим подпрограммам. Затем мы передаем этот идентификатор и идентификатор пользователя функции qjc-ta: :query(), чтобы получить текущие параметры квот. Нам нужны эти настройки, чтобы не нарушить ограничения, которые мы не будем изменять (например, число файлов). Наконец, мы устанавливаем квоту. Вот и все, всего лишь три строчки кода на Perl.
Помните, что девиз Perl TMTOWTDI означает «существует более одного способа сделать это», но это вовсе не значит «несколько одинаково хороших способов».
Различия файловых систем
Начнем с краткого обзора файловых систем, свойственных каждой из рассматриваемых операционных систем. Возможно, это не представляет для вас ничего нового, особенно, если у вас есть значительный опыт работы с какой-либо операционной системой. Но все же стоит обратить внимание на различия между файловыми системами (особенно на те, которые вам не знакомы), если вы собираетесь писать программы на Perl, работающие на разных платформах.
Unix
Все современные разновидности Unix поставляются с файловыми системами, семантика которых напоминает семантику их общего предка -файловой системы Berkeley Fast File System. Различные производители по-разному расширяли свои файловые системы (так, в Solaris добавили списки контроля доступа (Access Control Lists) для большей безопасности, в Digital Unix стали применять файловую систему advfs, основанную на транзакциях, и т. д.). Мы будем писать код, «приведенный к общему знаменателю», что позволит ему работать на различных Unix-платформах.
Вершина, или корень файловой системы, в Unix обозначается символом прямого слэша (/'). Для того чтобы уникальным образом идентифицировать файл или каталог в файловой системе, мы строим путь, начинающийся со слэша, и добавляем в него каталоги, разделяя их
слэшами, по мере прохода «вглубь» файловой системы. Последний компонент пути - нужный каталог или файл. В современных вариантах Unix имена каталогов и файлов чувствительны к регистру символов. При известном навыке в именах можно использовать практически все ASCII-символы, но если ограничиться буквенно-цифровыми символами и некоторыми знаками пунктуации, то в дальнейшем можно избежать лишних сложностей.
Microsoft Windows NT/2000
Windows NT (в данной книге речь идет о версии 4.0) поставляется с двумя поддерживаемыми файловыми системами: файловой системой FAT (таблица размещения файлов) и NTFS (файловая система NT). В Windows 2000 добавлена файловая система FAT32 - улучшенная версия FAT, позволяющая иметь разделы больших размеров и кластеры меньших размеров.
В Windows NT используется расширенная версия файловой системы FAT из DOS. Перед тем как рассматривать расширенную версию, очень важно разобраться в недостатках исходной. В обычной файловой системе (FAT реального режима) имена файлов должны соответствовать формату 8.3. Это означает, что имена файлов или каталогов могут содержать не более восьми символов, за которыми должна следовать точка, а затем суффикс длиной не более трех символов. В отличие от Unix, где точка в имени файла не имеет специального назначения, в FAT можно использовать только одну точку в качестве разделителя между именем файла и его расширением (суффиксом).
Позднее файловая система FAT была расширена до VFAT, или «FAT защищенного режима». Эта версия поддерживается в Windows NT и Windows 2000. VFAT скрывает от пользователей все ограничения, накладываемые на имена. Более длинные имена файлов без разделителей поддерживаются благодаря хитрому трюку. В VFAT используется цепь из нескольких стандартных слотов для имен файлов/каталогов, чтобы прозрачно встроить поддержку расширенных имен файлов в структуру обычной файловой системы FAT. В целях совместимости к каждому файлу и каталогу по-прежнему можно обратиться, используя псевдонимы в формате 8.3. Например, к каталогу с именем Downloaded Program Files можно обратиться, используя имя DOWNLO-1.
Между VFAT и файловыми системами Unix существуют четыре основные различия:
Файловые системы FAT не чувствительны к регистру. В Unix попытка открыть файл, используя неверный регистр символов (например MYFAVORITEFILE
вместо myfavoritefile), окончится неудачей. В FAT или VFAT это можно сделать без труда.
Второе различие - символы, выбранные для разделения компонент пути. В FAT в качестве разделителя вместо прямого слэша используется обратный слэш. При программировании в Perl это следует учитывать. В Perl обратный слэш является специальным симво лом. Пути с одинарными разделителями, заключенные в одинарные кавычки (т. е. $path= ' \dir\dir\fiienane '), не вызывают затруднений. Однако необходимость поставить несколько обратных слэшей рядом (например \\server\dir\file) может оказаться потенциально опасной. В таких случаях будьте бдительны и не забывайте удваивать идущие подряд обратные слэши. Некоторые функции и модули могут принимать пути с прямыми слэшами, но при программировании на это лучше не полагаться. Лучше перестраховаться и написать \\\\winnt\\teirip\\. . ., чем выяснить, что у вас ничего не получилось из-за того, что преобразование не было выполнено.
В FAT с файлами и каталогами связаны специальные флаги, назы ваемые
атрибутами. Примеры атрибутов - «Read-only» (только для чтения) и «System» (системный).
Наконец, последнее отличие - указание корневого каталога. Корень в FAT начинается с указания буквы диска, на котором располагается эта файловая система. Например, абсолютный путь к файлу может быть указан так c:\home\cindy\docs\resume\current.doc.
Файловые системы FAT32 и NTFS имеют ту же семантику, что и VFAT. В них одинаково реализована поддержка длинных имен файлов и используется один и тот же способ обозначения корневого каталога. Поддержка длинных имен в NTFS несколько сложнее, т. к. там разрешено использование Unicode в именах файлов. Unicode - это многобайтовая кодировка, которую можно применять для представления всех символов всех языков планеты.
В NTFS также есть несколько функциональных особенностей, отличающих ее от других файловых систем Windows NT/2000 и основных файловых систем Unix. NTFS поддерживает понятие списков контроля доступа (ACL). ACL предоставляет хорошо разграниченный механизм прав доступа к файлам и каталогам. Позже в этой главе мы приведем пример кода, использующего преимущества некоторых из этих отличий.
Перед тем как перейти к другой операционной системе, очень важно хотя бы упомянуть UNC - универсальное соглашение об именовании. UNC - это соглашение о расположении объектов (в нашем случае файлов и каталогов) в сетевом окружении. Вместо имени диска и двоеточия, с которых начинается абсолютный путь, часть
имя диска: заменяется на \\сервер\имя_ресурса. Это соглашение подвержено той же синтаксической двусмысленности обратного слэша в Perl, о которой мы уже говорили.
MacOS
Несмотря на GUI-ориентированный подход, иерархическая файловая система MacOS (HFS, Hierarchical File System) также позволяет указывать текстовые имена файлов, хотя для этого нужно немного изловчиться. Абсолютные пути задаются в следующем виде: Диск/Имя_тома:Папка:Папка:Папка:Имя_файла. Отсутствие двоеточий указывает на то, что файл находится в текущем каталоге.
В отличие от двух предыдущих операционных систем, пути в HFS считаются абсолютными, если не начинаются с разделителя пути (:). Путь HFS, начинающийся с двоеточия, является относительным. Есть небольшое отличие записи пути в MacOS по сравнению с другими файловыми системами - это количество разделителей, которое необходимо указывать при ссылке на объект, стоящий выше в иерархии каталогов. Например, в Unix используется ../../../FileNamc для обращения к файлу, находящемуся тремя уровнями выше текущего каталога. В MacOS понадобилось бы использовать четыре разделителя (т. е. File-Name), поскольку необходимо включить ссылку на текущий каталог помимо трех предыдущих уровней.
В HFS длина имен файлов и каталогов ограничена 31 символом. В MacOS версии 8.1 был введен альтернативный многосимвольный формат, названный расширенным форматом MacOS, или HFS+, для поддержки Unicode в именах файлов длиной до 255 символов. И хотя файловая система HFS+ позволяет использовать такие длинные имена, на момент написания этой книги они еще не поддерживаются в MacOS.
Еще более заметным отличием от предыдущих двух файловых систем (по крайней мере, с точки зрения программирования на Perl) является использование в MacOS понятия «fork» (ветвление) при хранении файлов. У каждого файла есть поток данных (data fork) и поток ресурсов (resource fork). В первом хранятся данные, а во втором содержатся различные ресурсы. В эти ресурсы могут входить исполняемый код (в случае, если это программа), определения пользовательского интерфейса (диалоговые окна, шрифты и т. д.) или любые другие компоненты, определяемые программистом. И хотя в этой главе мы не будем рассматривать ветвления, в MacPerl есть возможность чтения и записи в оба потока.
В MacPerl основные операторы и функции работают только с потоком данных. Например, оператор —s возвращает только размер потока данных файла. Если вы хотите обратиться к потоку ресурсов файла, вам придется использовать дополнительные модули, входящие в состав Macintosh Toolbox.
Каждый файл в файловой системе HFS также имеет два специальных тега:
creator (создатель) и type (тип), позволяющие операционной
системе идентифицировать, каким приложением был создан файл и какого он типа. Эти теги играют ту же роль, что и расширения, используемые в файловых системах FAT (например .doc или .ехе). Позже в этой главе мы увидим, как применять теги тип/создатель в собственных целях.
Сводка различий файловых систем
Ниже представлены те различия, о которых мы только что говорили, и некоторые другие интересные факты (табл. 2.1).
Таблица 2.1. Сравнение файловых систем
OS и файловая система |
Разде литель пути |
Чувстви тельность к регистру |
Длина имени файла |
Формат абсолют ного пути |
Формат относи тельного пути |
Уникальные возмож ности |
Unix (файловая система - Berkeley Fast File System и другие) |
/ |
Да |
В зависимости от операционной системы |
/dir/file |
dir/file |
Дополнения в зависимости от операционной системы |
MacOS (HFS) |
: |
Да |
31 символ (или 255 при использовании HFS+) |
volume: dir:file |
:dir:file |
Потоки данных/ ресурсов, атрибуты создатель/ тип |
WinNT/2000 (NTFS) |
\ |
Нет |
255 символов |
Drive:\ dir\file |
dir\file |
ACL, атрибуты, Unicode в именах файлов |
DOS (BASIC FAT) |
\ |
Нет |
8.3 |
Drive:\ dir\file |
dir\file |
Атрибуты |
Учет различий файловых систем в Perl
Perl может помочь создавать программы, в которых учитывается большинство особенностей файловых систем. В его состав входит модуль File::Spec, позволяющий нивелировать некоторые различия между файловыми системами. Например, если мы передаем компоненты пути методу catfile таким образом:
use File: :Spec
Path = File: : Spec -> cat r iie( "how1 . "iocs" ' г eSi;i:C doc")
то в Windows NT/2000 переменная $path будет иметь значение home\cindy\docs\resume. doc, тогда как в Unix она будет иметь значение cindy/docs/resume.doc и т. д. В модуле File: :Spec также есть методы, например curdir и updir, возвращающие обозначения для текущего и родительского каталогов (например « » и « »). Методы этого модуля предоставляют абстрактный способ построения и манипулирования именами путей. Если вы предпочитаете не использовать объектно-ориентированный синтаксис, то модуль File: :Spec: : Functions предоставляет более короткий путь к методам из File: Spec.