Глава 12В этой главе:
Развертывание Дескрипторы каталогов Открытие и закрытие дескриптора каталога Чтение дескриптора каталога Упражнения Доступ к каталогамПеремещение по дереву каталогов Вы уже, вероятно, знакомы с понятием "текущий каталог" и с тем, как использовать команду cd shell. В системном программировании для изменения текущего каталога процесса вы производили бы системный вызов chdir. В Perl тоже используется это имя.Функция chdir в Perl принимает один аргумент выражение; при его вычислении определяется имя каталога, который становится текущим. Как и в большинстве других системных вызовов, при успешном изменении текущего каталога на затребованный chdir возвращает значение "истина", а при неудачном исходе "ложь". Вот пример:chdir("/etc") || die "cannot cd to /etc ($!)"; Круглые скобки не обязательны, поэтому можно обойтись и такой записью: print "where do you want to go? "; chomp($where = <STDIN>) ; if (chdir $where) f # получилось ) else {# не получилось >Вы не сможете определить, где вы находитесь, не запустив команду pwcf. О запуске команд мы расскажем в главе 14.* Или не использовав функцию getcwd () из модуля Cwd.Для каждого процесса* назначается свой текущий каталог. Когда запускается новый процесс, он наследует текущий каталог своего родительского процесса, но на этом вся связь заканчивается. Если Perl-программа меняет свой каталог, это не влияет на родительский shell (или иной объект), который запустил этот Perl-процесс. Точно так же процессы, создаваемые Perl-программой, не влияют на текущий каталог самой этой программы. Текущие каталоги для новых процессов наследуются от текущего каталога Perl-программы.По умолчанию функция chdir без параметра делает текущим начальный каталог, почти так же, как команда cd shell.Развертывание Если в качестве аргумента в командной строке стоит звездочка (*), то shell (или тот интерпретатор командной строки, которым вы пользуетесь) интерпретирует ее как список имен всех файлов, находящихся в текущем каталоге. Так, если вы дали команду rm *, то она удалит все файлы из текущего каталога. (Не делайте этого, если не хотите потом долго приставать к системному администратору с просьбами о восстановлении файлов.) Еще примеры: аргумент [а-т] *. с превращается в список имен файлов, находящихся в текущем каталоге, которые начинаются с одной из букв первой половины алфавита и заканчиваются на .с, а аргумент /etc/host* в список имен файлов каталога /etc, которые начинаются со слова host. (Если для вас это ново, то перед дальнейшей работой рекомендуем почитать дополнительную литературу о сценариях shell.)Преобразование аргументов вроде * или /etc/host* в список соответствующих имен файлов называется развертыванием (globbing). Perl обеспечивает развертывание с помощью очень простого механизма: подлежащий развертыванию образец заключается в угловые скобки или же используется функция glob.@а = </etc/host*> @а = glob("/de/host*");В списочном контексте, как показано выше, результатом развертывания является список всех имен, которые совпадают с образцом (как если бы shell раскрыл указанные аргументы), или пустой список, если совпадения не обнаружено. В скалярном контексте возвращается следующее совпадающее имя, а если совпадений нет возвращается undef; это очень похоже на чтение из дескриптора файла. Например, просмотр имен по одному реализуется с помощью следующего кода:while (defined($nextname = </etc/host*>)) ( print "one of the files is $nextname\n"; > * Это справедливо для UNIX и большинства других современных операционных систем.Здесь возвращенные имена файлов начинаются с префикса, соответствующего пути доступа к ним (/etc/host), поэтому, если вам нужна только последняя часть имени, придется выделять ее самостоятельно, например:while ($nextname = </etc/host*>) ( $nextname =~ s#.*/##; # удалить часть до последней косой чертыprint "one of the files is $nextname\n"; } Внутри аргумента допускается указывать несколько образцов; эти списки развертываются отдельно, а затем конкатенируются так, как будто это один большой список: @fred_barney_files = <fred* barney*>; Другими словами, эта операция развертывания выдает те же значения, которые возвратила бы эквивалентная ей команда echo с такими же параметрами.*Хотя развертывание списка файлов и сопоставление с регулярным выражением осуществляются почти одинаково, специальные символы имеют совершенно разный смысл. Не путайте эти механизмы, иначе будете удивляться, почему вдруг <\ . с$> не находит все файлы, имена которых заканчиваются на .с!В аргументе функции glob перед развертыванием производится интерполяция переменных. С помощью Perl-переменных можно выбирать файлы, задаваемые строкой, вычисляемой во время выполнения:if (-d. "/usr/etc") ( $where = "/usr/etc"; } else ( $where = "/etc"; ) Sfiles = <$where/*>; Здесь переменной $ where присваивается одно из двух имен каталогов в зависимости от того, существует каталог /usr/etc или нет. Затем мы получаем список файлов, находящихся в выбранном каталоге. Обратите внимание:переменная $where является развертываемой, т.е. подлежащими развертыванию символами являются /etc/* или /usr/etc/*.Есть одно исключение из этого правила: образец <$var> (который означает использование в качестве развертываемого выражения переменной $var) должен записываться как <${var}>. В причины появления этого исключения сейчас лучше не вдаваться.*** Для вас не будет сюрпризом узнать о том, что для выполнения развертывания Perl просто запускает C-shell, который раскрывает указанный список аргументов, и производит синтаксический анализ того, что получает обратно.** Конструкция <$fred> читает строку из дескриптора файла, заданного содержимым скалярной переменной $ fred. Наряду с некоторыми другими особенностями, не упомянутыми в нашей книге, эта конструкция позволяет использовать косвенные дескрипторы файлов, где имя дескриптора передается и обрабатывается так, как будто это данные.Дескрипторы каталогов Если в вашей конкретной разновидности операционной системы имеется библиотечная функция readdir или ее функциональный эквивалент, Perl обеспечивает доступ к этой программе (и ее спутницам) с помощью дескрипторов каталогов. Дескриптор каталога это имя из отдельного пространства имен, и предостережения и рекомендации, которые касаются дескрипторов файлов, касаются также и дескрипторов каталогов (нельзя использовать зарезервированные слова, рекомендуется использовать верхний регистр). Дескриптор файла fred и дескриптор каталога fred не связаны между собой.Дескриптор каталога представляет собой соединение с конкретным каталогом. Вместо чтения данных (как из дескриптора файла) вы используете дескриптор каталога для чтения списка имен файлов в этом каталоге. Дескрипторы каталогов всегда открываются только для чтения; нельзя использовать дескриптор каталога для изменения имени файла или удаления файла. Если функции readdir() и ее аналогов в библиотеке нет (и при инсталляции языка Perl никакую замену вы не предусмотрели), то использование любой из этих программ приведет к фатальной ошибке и ваша программа компиляцию не пройдет: она аварийно завершится до выполнения первой строки кода. Perl всегда старается изолировать вас от влияния рабочей среды, но такие чудеса ему не подвластны.Открытие и закрытие дескриптора каталога Функция opendir работает аналогично библиотечному вызову с тем же именем в С и C++. Ей дается имя нового дескриптора каталога и строковое значение, задающее имя открываемого каталога. Если каталог может быть открыт, opendir возвращает значение "истина"; в противном случае она возвращает "ложь". Вот пример:opendir(ETC,"/etc") || die "Cannot opendir /etc: $!"; После этого обычно следуют разного рода манипуляции с дескриптором каталога etc, но сначала, наверное, нужно разобраться, как закрывать дескриптор каталога. Это делается с помощью функции closedir, которая весьма похожа на close:closedir(ETC); Как и close, closedir часто оказывается ненужной, потому что все дескрипторы каталогов автоматически закрываются перед повторным открытием либо в конце программы.Чтение дескриптора каталога Открыв дескриптор каталога, мы можем прочитать список имен с помощью функции readdir, которая принимает единственный параметр дескриптор каталога. Каждый вызов readdir в скалярном контексте возвращает следующее имя файла (только основное имя: в возвращаемом значении никаких косых нет) в порядке, который на первый взгляд кажется случайным*. Если больше имен нет, readdir возвращает undef**. Вызов readdir в списочном контексте возвращает все оставшиеся имена файлов в виде списка с одним именем, приходящимся на каждый элемент. Вот пример, в котором выдается перечень всех имен файлов, содержащихся в каталоге /etc:opendir(ETC,"/etc") II die "no etc?: $!"; while ($name = readdir(ETC)) f t скалярный контекст, по одному на циклprint "$name\n"; #выводит ., .., passwd, group и т.д. 1 closedir(ETC) ;А вот как можно получить все имена в алфавитном порядке с помощью функции sort:opendir(ETC,"/etc") || die "no etc?: $!"; foreach $name (sort readdir(ETC)) ( # списочный контекст с сортировкойprint "$name\n"; #выводит ., .., passwd, group и т.д. ) closedir(ETC) ;В этот список включены имена файлов, которые начинаются с точки. Это не похоже на результат развертывания, выполненного с использованием <*>, при котором имена, начинающиеся с точки, не возвращаются. С другой стороны, это похоже на результат работы команды echo* shell.* Точнее говоря это порядок, в котором имена файлов расположены в каталоге, т.е. тот же "беспорядочный порядок", в котором вы получаете файлы в ОС UNIX в результате вызова команды .find или Is -f.** Это означает, что при работе с опцией-w вам придется использовать цикл while (defined($name = readdir (...)). Упражнения 1. Напишите программу, которая открывает каталог, заданный ее параметром, а затем выдает список имен файлов этого каталога в алфавитном порядке. (В случае, если переход в каталог не может быть выполнен, программа должна предупредить об этом пользователя.) "2. Модифицируйте программу так, чтобы она выдавала имена всех файлов, а не только те, имена которых не начинаются с точки. Попробуйте сделать это как с помощью операции развертывания, так и посредством использования дескриптора каталога. |