Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель PHP 4 - Котеров Д. В

..pdf
Скачиваний:
93
Добавлен:
24.05.2014
Размер:
4.38 Mб
Скачать

Глава 16

Работа с каталогами

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

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

Манипулирование каталогами

Вот несколько полезных функций для работы с каталогами.

bool mkdir(string $name, int $perms)

Создает каталог с именем $name и правами доступа $perms. Права доступа для каталогов указываются точно так же, как и для файлов. Чаще всего значение $perms устанавливают равным 0770 (предваряющий ноль обязателен — он указывает PHP на то, что это — восьмеричная константа, а не десятичное число). Например:

mkdir("my_directory",0755); // создает подкаталог в текущем каталоге mkdir("/data"); // создает подкаталог data в корневом каталоге

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

Вы, наверное, заметили, что атрибуты доступа 0770 означают "доступен для чтения, записи и исполнения для владельца и его группы". Что означает атри- бут исполнения, установленный для каталога? Может быть, он разрешает пользователям запускать из него программы? А вот и нет. Право на "исполне-

Глава 16. Работа с каталогами

269

ние" показывает, что пользователь сможет просмотреть содержимое катало- га. Конечно, все это специфично для операционных систем семейства Unix.

bool rmdir(string $name)

Удаляет каталог с именем $name. В случае успеха возвращает true, иначе — false. Как всегда, действуют стандартные ограничения файловой системы на эту операцию.

bool chdir(string $path)

Сменяет текущий каталог на указанный. Если такого каталога не существует, возвращает false. Параметр $path может определять и относительный путь, задающийся от текущего каталога. Вот несколько примеров:

chdir("/tmp/data");

// переходим по абсолютному пути

chdir("./somathing");

// переходим в подкаталог текущего каталога

chdir("something");

// то же самое

chdir("..");

// переходим в родительский каталог

chdir("~/data");

// переходим в /home/ПОЛЬЗОВАТЕЛЬ/data (для Unix)

string getcwd()

 

Возвращает полный путь к текущему каталогу, начиная от "корня" (/). Если такой путь не может быть отслежен (это иногда бывает в Unix из-за того, что права на чтение для родительских каталогов могут быть сняты), вызов "проваливается" и возвра-

щает false.

Эта функция появилась в PHP совсем недавно. Так что если ее не окажется в вашей версии, обновите ее поскорее, либо напишите заменитель (что не так-то просто).

Работа с записями

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

int opendir(string $path)

Открывает каталог $path для дальнейшего считывания из него информации о файлах и подкаталогах и возвращает его идентификатор. Дальнейшие вызовы readdir() с идентификатором в параметрах будут обращены именно к этому каталогу. Функция возвращает false, если произошла ошибка.

270

Часть IV. Стандартные функции PHP

string readdir(int $handle)

Считывает очередное имя файла или подкаталога из открытого ранее каталога с идентификатором $handle и возвращает его в виде строки. Порядок следования файлов в каталоге зависит от операционной системы — скорее всего, он будет совпадать с тем порядком, в котором эти файлы создавались, но не всегда. Вместе с именами подкаталогов и файлов будут также получены два специальных элемента: это . (ссылка на текущий каталог) и .. (ссылка на родительский каталог). В подавляющем большинстве случаев нам нужно их игнорировать, что и сделано в примере из листинга 16.1 при помощи инструкции continue.

PHP версии 3 позволял опускать параметр $handle в этом случае, кажется, подразумевался последний открытый каталог. Сценарий "собирался с силами", "вздыхал" икое-как работал. PHP версии 4 более строг: в нем вы обязатель- но должны указывать параметр $handle для функции readdir(), в против- ном случае вам гарантированы сюрпризы.

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

$d=opendir("somewhere"); while($e=readdir($d)) { . . .}

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

$d=opendir("somewhere"); while(($e=readdir($d))!==false) { . . .}

Оператор !== позволяет точно проверить, была ли возвращена величина false.

void closedir(int $handle)

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

void rewinddir(int $handle)

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

Пример: печать дерева каталогов

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

Глава 16. Работа с каталогами

271

Листинг 16.1. Печать дерева каталогов в файловой системе

<?

//Функция распечатывает имена всех подкаталогов в текущем каталоге,

//выполняя рекурсивный обход. Параметр $level задает текущую

//глубину рекурсии.

function PrintTree($level=1)

{

// Открываем каталог и выходим в случае ошибки $d=@opendir(".");

if(!$d) return; while(($e=readdir($d))!==false) {

//Игнорируем элементы .. и . if($e=='.'||$e=='..') continue;

//Нам нужны только подкаталоги if(!@is_dir($e)) continue;

//Печатаем пробелы, чтобы сместить вывод for($i=0; $i<$level; $i++) echo " ";

//Выводим текущий элемент

echo "$e\n";

// Входим в текущий подкаталог и печатаем его if(!chdir($e)) continue; PrintTree($level+1);

//Возвращаемся назад chdir("..");

//Отправляем данные в браузер, чтобы избежать видимости зависания

//для больших распечаток

flush();

}

closedir($d);

}

//Выводим остальной текст фиксированным шрифтом echo "<pre>";

echo "/\n";

//Входим в корневой каталог и печатаем его chdir("/");

PrintTree(); echo "</pre>"; ?>

272

Часть IV. Стандартные функции PHP

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

Последний факт делает метод рекурсивного обхода каталогов совершенно не- пригодным для автоматического построения карты сервера. В случае приме-

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

Глава 17

Каналы и символические ссылки

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

rИ то и другое сколько-нибудь результативно можно использовать лишь в системах на основе Unix, в других же операционных системах функции либо не реализованы, либо просто не работают.

rПримерно лишь один сценарий на PHP из тысячи может нуждаться в создании и использовании каналов и символических ссылок.

Каналы

Давайте начнем с каналов. Мы уже привыкли, что можем открыть какой-то файл для чтения при помощи fopen(), а затем читать или писать в него данные. Теперь представьте себе немного отвлеченную ситуацию: вы хотите из сценария запустить какуюто внешнюю программу (скажем, утилиту mail для отправки или приема почты). Вам нужен будет механизм, посредством которого вы могли бы передать этой утилите данные (например, E-mail и текст письма), а затем получить результат работы программы.

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

//Запускаем процесс /bin/ls (параллельно работе сценария) в режиме

//чтения. Эта утилита Unix просто распечатывает содержимое текущего

//каталога, а ключ -l заставляет ее детализировать распечатку.

274

Часть IV. Стандартные функции PHP

$fp=popen("/bin/ls -l","r");

//Теперь мы можем работать с $fp как с обычным файловым

//идентификатором. То есть выполнять функции чтения. for($Lines=array(); !eof($fp);)

$Lines[]=fgets($fp,1000);

//Не забудем также закрыть канал.

pclose($fp);

Теперь более подробно. По команде popen() запускается указанная в первом параметре программа, причем выполняется она параллельно сценарию. Соответственно, управление сразу возвращается на следующую строку, и сценарий не ждет, пока завершится наша утилита (в отличие от функции system(), которая будет вскоре рассмотрена). Второй параметр задает режим работы: чтение или запись, точно так же, как это делается в функции fopen().

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

Далее в нашем примере происходит вот что. Стандартный вывод утилиты (тот, который по умолчанию всегда является просто выводом на экран — да простят меня поклонники Unix, но все-таки так будет проще объяснить, не разъясняя, что такое перенаправление ввода-вывода и какую роль оно играет в этой ОС) прикрепляется к идентификатору $fp. Теперь все, что печатает утилита (а в нашем случае она печатает содержимое каталога), может быть прочитано при помощи обычных вызовов файловых функций чтения — fgets(), fread() и т. д.

Программа ls не ждет никакого ввода (однако в общем случае это далеко не всегда так), вот почему мы пользуемся только серией вызовов fgets(). После того, как "дело сделано", канал $fp, вообще говоря, нужно закрыть. Если он ранее был открыт в режиме записи, утилите "на том конце" передается, что ввод данных "с клавиатуры" завершен, и она может закончить свою работу.

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

Глава 17. Каналы и символические ссылки

275

Теперь, когда мы разобрались с каналами, давайте посмотрим, что предлагает нам PHP для работы с символическими ссылками.

Символические ссылки

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

зываются символическими ссылками.

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

работают fopen(), fread()

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

string readlink(string $linkname)

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

bool symlink(string $target, string $link)

Эта функция создает символическую ссылку с именем $link на объект (файл или каталог), заданную в $target. В случае "провала" функция возвращает false.

array lstat(string $filename)

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

int linkinfo(string $linkname)

Функция возвращает значение поля "устройство" из результата, выдаваемого функцией lstat(), которую мы рассматривали выше. Ее обычно задействуют, если хотят определить, существует ли еще объект, на который указывает символическая ссылка в $linkname. Я предпочитаю пользоваться для этого вызовом stat(), т. к., помоему, ее название несколько более "читабельно".

276

Часть IV. Стандартные функции PHP

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

Жесткие ссылки

И в конце этой главы я хочу рассмотреть еще один вид ссылок — жесткие ссылки. Оказывается, создание символической ссылки — не единственный способ задать для одного файла несколько имен. Главный недостаток символических ссылок, как вы, наверное, уже догадались, — существование основного имени файла, на которое все и ссылаются. Попробуйте удалить этот файл — и вся "паутина" ссылок, если таковая имелась, развалится на куски. Есть и другой недостаток: открытие файла, на который указывает ссылка, происходит несколько медленнее, т. к. системе нужно проанализировать содержимое ссылки и установить связь с "настоящим" файлом. Особенно это чувствуется, если одна ссылка указывает на другую, та — на третью и т. д. уровней на 10.

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

Зарегистрировать новое имя у файла (то есть создать для него жесткую ссылку) можно с помощью функции link(). Ее синтаксис полностью идентичен функции symlink(), да и работает она по тем же правилам, за исключением того, что создает не символическую, а жесткую ссылку. Фактически, вызов link() — это почти то же, что и rename(), только старое имя файла не удаляется, а остается.

Напоминаю, работает это все только в Unix, но не в Windows. И почему только до таких вещей не додумались парни из Microsoft?..

Глава 18

Запуск внешних программ

Функции запуска внешних программ в PHP востребуются достаточно редко. Их "непопулярность" объясняется прежде всего тем, что при использовании PHP программист получает в свое распоряжение почти все возможности, которые могут когдалибо понадобиться, в частности, почтовые функции, на которые приходится львиная доля вызовов внешних программ в других языках — например, в Perl. Тем не менее, в числе стандартных функций языка присутствует полный набор средств, предназначенных для запуска программ и утилит операционной системы.

string system(string $command [,int& return_var])

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

Впрочем, задействуя функции перенаправления вывода, мы все-таки можем получить и обработать то, что выдала нам запущенная программа, но стоит ли игра свеч? Может быть, лучше воспользоваться более подходящими средст- вами?

Если функции передан также второй параметр — переменная (именно переменная, а не константа!), то в нее помещается код возврата вызванного процесса. Ясно, что это требует от PHP ожидания завершения запущенной программы — так он и поступает в любом случае, даже если последний параметр не задан.

Не нужно и говорить, что при помощи этой функции можно запускать только те команды, в которых вы абсолютно уверены. В частности, никогда не переда- вайте функции system() данные, пришедшие из браузера пользователя (предварительно не обработав их) — это может нанести серьезный урон ва- шему серверу, если злоумышленник запустит какую-нибудь разрушительную утилиту например, rm -R ~/, которая быстро и "без лишних слов" очистит весь ваш каталог.