Работа с файлами и каталогами Глава 5

advertisement
Глава 5
Работа с файлами
и каталогами
Конструкции include и require —
в чем различие?
Начиная главу, посвященную файлам, следует остановиться на инструкциях
include() и require(), позволяющих включить файл в PHP-скрипт. Обе
функции принимают единственный аргумент — путь к включаемому файлу,
и результатом их действия является подстановка содержимого файла в место
их вызова в исходном скрипте. Если в качестве включаемого скрипта выступает PHP-скрипт, то сначала происходит его подстановка в исходный скрипт,
а затем интерпретация результирующего скрипта (листинг 5.1).
Листинг 5.1. Использование инструкции include
<?php
echo "first<br>";
include ("second.php");
echo "first<br>";
?>
Пусть файл second.php содержит код, представленный в листинге 5.2.
Листинг 5.2. Файл second.php
<?php
echo "second<br>";
?>
<h3>Текст не обязательно должен выводиться оператором echo</h3><br>
160
Глава 5
Рис. 5.1. Результат работы скрипта с инструкцией include
Результат вызова скрипта, приведенного в листинге 5.1, представлен на рис. 5.1.
Выполнение включаемого скрипта second.php можно прекратить досрочно,
объявив во включаемом файле оператор return, точно так же как при работе
с функциями (листинг 5.3).
Листинг 5.3. Файл second.php
<?php
return;
echo "second";
?>
<h3>Текст не обязательно должен выводиться оператором echo</h3><br>
Так после исправления содержимого файла second.php из листинга 5.2 на содержимое, представленное в листинге 5.3, результат работы скрипта будет
выглядеть так, как это показано на рис. 5.2.
Следует обратить внимание на то, что вызов оператора return действует точно так же, как в обычных функциях — программа немедленно выходит из
файла, а все последующие за return операторы игнорируются. Однако внешний скрипт, вызывающий оператор include, свою работу не прекращает.
ЗАМЕЧАНИЕ
В качестве пути к файлу может быть указан сетевой путь. В этом случае следует помнить, что функция include() вернет файл в том виде, в котором она
Работа с файлами и каталогами
161
получает его от Web-сервера, т. е. не следует ждать PHP-код — в сетевом варианте функцией include() будет получен лишь результат работы скрипта, но
не его код.
Рис. 5.2. Досрочное прекращение выполняемого файла
Включаемый функцией include() файл не обязательно должен быть PHPскриптом, к этой функции часто прибегают для включения объемных текстовых вставок, которые бы могли затруднить чтение кода.
Как было упомянуто в начале раздела, помимо функции include(), существует функция require(), выполняющая аналогичные действия. Различия
в этих функциях заключается в их реакции на отсутствие включаемого файла.
Если в случае функции include() включаемый файл отсутствует, то реакцией на это является единственно вывод в окно браузера соответствующего
предупреждения, которое можно подавить, разместив перед include() символ @, вызывающий подавление вывода сообщений об ошибках в окно браузера. Отсутствие файла по пути, который передается в качестве аргумента
функции require(), приводит к остановке скрипта.
ЗАМЕЧАНИЕ
Для обеих функций существуют аналоги с суффиксом _once: include_once()
и require_once(), позволяющие включить файл в документ только один раз,
не зависимо от того, сколько попыток включения предпринимается. Это удобно
использовать при вложенных включениях, во избежание ошибок при повторном
включении файлов, содержащих объявления функций.
При первом знакомстве с операторами include() и require() возникает соблазн передавать путь к файлам через GET-параметры, которые затем переда-
162
Глава 5
вать непосредственно операторам include() или require(). Однако такой
подход таит в себе опасность, т. к. злоумышленник получает возможность
выполнения произвольного PHP-кода на сервере. Рассмотрим уязвимость более подробно, для этого создадим файл index.php с содержимым, представленным в листинге 5.4.
Листинг 5.4. Передача GET-параметра оператору include()
<?php
@include ($_GET['path']);
?>
Теперь создав множество файлов req1.php, req2.php, ..., req3.php, мы получаем возможность динамически подключать их, просто указывая их имена в GET-параметре
path строки запроса, например, http://www.site.ru/index.php?path=req2.php.
Однако злоумышленник может подставить в качестве пути к файлу свой собственный путь, в том числе и сетевой, например, http://www.site.ru/
index.php?path=http://www.xaker.ru/q.php. Если файл имеет расширение
php, ничего страшного не произойдет, будет подставлен результат выполнения PHP-файла, т. е. HTML-код, при помощи которого деструктивные действия невозможны. Однако если вместо PHP-файла будет передан txt-файл,
с PHP-инструкциями — они будут выполнены. Причем принудительное добавление расширения файла, как это представлено в листинге 5.5, не устраняет уязвимости.
Листинг 5.5. Принудительное назначение расширения включаемого файла
<?php
@include ($_GET['path'].".php");
?>
В этом случае злоумышленник может создать PHP-скрипт, который вместо
HTML-кода генерирует текст другого PHP-скрипта, или назначить на своем
сервере в качестве MIME-типа PHP-файлов текстовый тип text/plain.
Открытие файлов
Любая операция с файлами, затрагивающая его содержимое, будь то создание
нового файла, чтение или запись в уже существующий файл, начинается операцией открытия файла. Исключение составляет ряд операций, которые об-
Работа с файлами и каталогами
163
суждаются далее. Открытие файлов производится при помощи функции
fopen(), которая имеет следующий синтаксис:
int fopen(string filename, string mode [, int use_include_path])
Первый аргумент filename — относительный или абсолютный путь к файлу.
Второй аргумент mode позволяет задать режим работы с открываемым файлом и может принимать следующие значения:
ˆ r — открыть файл только для чтения; после открытия указатель файла ус-
танавливается в начало файла;
ˆ r+ — открыть файл для чтения и записи; после открытия указатель файла
устанавливается в начало файла;
ˆ w — создать новый пустой файл только для записи; если файл с таким
именем уже есть, вся информация в нем уничтожается;
ˆ w+ — создать новый пустой файл для чтения и записи; если файл с таким
именем уже есть, вся информация в нем уничтожается;
ˆ a — открыть файл для дозаписи; данные будут записываться в конец
файла;
ˆ a+ — открыть файл для дозаписи и чтения данных; данные будут записы-
ваться в конец файла;
ˆ b — флаг, указывающий на работу (чтение и запись) с двоичным файлом;
применяется только в Windows.
Третий необязательный аргумент use_include_path определяет, должны ли
искаться файлы в каталоге include_path. (Параметр include_path устанавливается в конфигурационном файле php.ini.)
В случае удачного открытия файла, функция fopen() возвращает дескриптор
файла, в случае неудачи — false. Дескриптор файла представляет собой
указатель на открытый файл, который используется операционной системой
для поддержки операций с этим файлом и представляет собой уникальное
число.
ЗАМЕЧАНИЕ
Не следует путать дескриптор файла и файловый указатель, который предназначен для указания положения точки ввода/вывода относительно начала открытого файла.
Возвращенный функцией дескриптор файла необходимо затем указывать во
всех функциях, которые в дальнейшем будут работать с этим файлом.
Код в листинге 5.6 создает файл file.txt.
164
Глава 5
Листинг 5.6. Создание файла
<?php
$fd = fopen("file.txt", "w");
if(!$fd) exit("Ошибка открытия файла");
?>
После выполнения скрипта в каталоге, где он расположен, должен появиться
файл с именем file.txt.
Открытие двоичного файла, к примеру, рисунка, происходит таким же образом, только с флагом b (листинг 5.7).
Листинг 5.7. Открытие двоичного файла для чтения
<?php
$fd = fopen("image.jpg", "rb");
if(!fd) exit("Ошибка открытия файла");
?>
В листингах 5.6 и 5.7 файлы создаются в том же каталоге, где расположен
PHP-скрипт. Если требуется открыть файл в произвольном каталоге, следует
либо указать абсолютный путь, либо воспользоваться относительным путем.
Абсолютный путь начинается либо с диска (в Windows), либо с корневого
каталога / в UNIX (листинг 5.8).
Листинг 5.8. Открытие файла с указанием абсолютного пути
<?php
$fd = fopen("D:/main/test/file.txt", "w");
if(!$fd) exit("Ошибка открытия файла");
?>
Допускается использование относительных путей. При их формировании
следует учитывать, что текущий каталог обозначается одной точкой (.),
а каталог, расположенный на один уровень выше, обозначается двумя точками (..). Например, если скрипт расположен в каталоге D:\main\test\, а файл
необходимо создать в каталоге D:\main\files\, то скрипт создания файла будет
выглядеть так, как это представлено в листинге 5.9.
Работа с файлами и каталогами
165
Листинг 5.9. Открытие файла с указанием относительного пути
<?php
$fd = fopen("../files/file.txt", "w");
if(!$fd) exit("Ошибка открытия файла");
?>
В листинге 5.9 мы поднимается на один каталог вверх и затем спускаемся
в каталог files. Подниматься можно и на большее число каталогов, например,
вполне допускается следующий путь: ../../file.txt.
Закрытие файлов
После того как работа с файлом закончена, его необходимо закрыть. Закрытие файлов осуществляется с помощью функции fclose(), которая имеет
следующий синтаксис:
int fclose (int fd)
Аргумент fd представляет собой дескриптор файла, который необходимо закрыть. В листинге 5.10 представлен скрипт, который создает файл file.txt
и закрывает его дескриптор.
ЗАМЕЧАНИЕ
Если скрипт не закрывает файл, то его закрытие осуществляется автоматически при завершении работы скрипта. Тем не менее, по возможности следует закрывать файл сразу же, как с ним прекращена работа, т. к. в то время, когда
файл открыт, с ним не может работать другой скрипт, точно так же как нельзя
сменить режим работы с файлом в текущем скрипте. Кроме того, запись в файл
буферизируется, т. е. если в файл помещаются новые данные, это вовсе не
значит, что они сразу попадают на диск (в противном случае операции бы записи были слишком медленными), вызов функции fclose() гарантирует, что все
данные будут перемещены из буферов на диск.
Листинг 5.10. Использование функции fclose()
<?php
$fd = fopen("file.txt", "w");
if(!$fd) exit("Ошибка открытия файла");
fclose($fd);
?>
166
Глава 5
Запись в файл
Запись в файл осуществляется функциями fputs() и fwrite(), которые абсолютно идентичны друг другу:
int fputs (int fd, string str [, int length]))
int fwrite (int fd, string str [, int length]))
Первый аргумент, fd, представляет собой дескриптор файла, который возвращается функцией fopen(). Второй аргумент, str, представляет собой
строку, которая должна быть записана в файл. Третий необязательный аргумент задает количество символов в строке, которые должны быть записаны.
Если третий аргумент не указан, записывается вся строка.
В листинге 5.11 в файл file.txt записывается строка "Hello, world!".
Листинг 5.11. Запись в файл
<?php
// Открываем файл для чтения
$fd = fopen ("file.txt", "w");
// Записываем файл
fwrite($fd, "Hello, world!");
// Закрываем файл
fclose ($fd);
?>
Помимо представленных функций, начиная с PHP 5.0.0, доступна функция
file_put_contents(), которая позволяет записать в файл строку без открытия файла при помощи функции fopen(). Функция имеет следующий синтаксис:
int file_put_contents (string filename, string data [, int flags])
В качестве первого параметра filename функция принимает имя файла, через
второй параметр data передается строка, которую необходимо записать
в файл. Третий необязательный параметр flags может принимать следующие
флаги:
ˆ FILE_USE_INCLUDE_PATH — искать файл в каталогах, определенных в кон-
фигурационном файле php.ini для библиотек;
ˆ FILE_APPEND — не затирать содержимое файла, а добавить новую строку
в конец файла.
Работа с файлами и каталогами
167
С использованием функции file_put_contents() код из листинга 5.11 можно переписать так, как это представлено в листинге 5.12.
Листинг 5.12. Запись в файл при помощи file_put_contents()
<?php
file_put_contents("file.txt", "Hello, world!");
?>
Чтение из файла
Чтение содержимого открытого файла можно осуществить при помощи
функции fread(), которая имеет следующий синтаксис:
string fread (int fd, int length)
Эта функция принимает в качестве первого аргумента fd дескриптор открытого файла, а в качестве второго — длину строки length, которую следует
прочитать из файла (в байтах). Для чтения файла целиком, часто прибегают
к функции filesize(), которая возвращает число байтов, содержащихся
в файле, имя которого передано данной функции в качестве аргумента. Скрипт
в листинге 5.13 считывает содержимое файла и выводит его в окно браузера.
Листинг 5.13. Чтение из файла
<?php
// Имя файла
$filename = "file.txt";
// Открываем файл для чтения
$fd = fopen($filename, "r");
// Читаем содержимое файла в
// переменную $bufer
$bufer = fread($fd, filesize($filename));
// Закрываем файл
fclose($fd);
// Выводим содержимое файла в окно браузера
echo $bufer;
}
?>
168
Глава 5
Если необходимо поместить все содержимое файла в переменную, удобнее
воспользоваться функцией file_get_contents(), которая имеет следующий
синтаксис:
string file_get_contents (string filename [, int use_include_path])
Функция принимает в качестве обязательного параметра filename имя
файла, а в качестве необязательного use_include_path — константу
FILE_USE_INCLUDE_PATH, если файл необходимо искать в каталогах, предназначенных для библиотек. В листинге 5.14 содержимое файла file.txt помещается в переменную $text.
Листинг 5.14. Использование функции file_get_contents()
<?php
$text = file_get_contents("file.txt");
?>
В качестве аргумента файловых функций можно указывать не только локальные переменные, но также и сетевые пути (листинг 5.15).
Листинг 5.15. Открытие сетевых путей
<?php
// Использование функций fopen(), fread() и fclose() для
// чтения содержимого сетевого источника
$fd = fopen("http://www.softtime.ru", "r");
// Читаем содержимое файла в
// переменную $bufer
$bufer = fread($fd, filesize($filename));
// Закрываем файл
fclose($fd);
// Использование функции file_get_contents() для
// чтения сетевого источника
$text = file_get_contents("http://www.php.net");
?>
Для чтения из файла можно также пользоваться функцией fgets(), которая
имеет следующий синтаксис:
string fgets(int file, int length)
Работа с файлами и каталогами
169
Эта функция читает и возвращает строку длиной length – 1 байтов. Чтение
прекращается, когда достигнута новая строка или конец файла. При достижении конца файла функция возвращает пустую строку. В листинге 5.16 демонстрируется наиболее типичное использование функции fgets().
Листинг 5.16. Использование функции fgets()
<?php
// Имя файла
$filename = "file.txt";
// Открываем файл для чтения
$fd = fopen($filename, "r");
// В цикле последовательно помещаем содержимое файла в
// переменную $bufer построчно
while (!feof ($fd))
{
$bufer .= fgets($fd, 4096);
}
// Закрываем файл
fclose($fd);
?>
Функция feof() на каждой из итераций проверяет, не достигнут ли конец
цикла, если достигнут, функция возвращает true, если нет — false. Обычно
строки в файле меньше 4096 символов, поэтому, по сути, скрипт из листинга
5.14 перебирает файл построчно. Это особенно удобно при работе с объемными файлами, размер которых может достигать несколько десятков мегабайт. Дело в том, что под каждый PHP-скрипт отводится конкретное количеmemory_limit
ство
памяти,
которое
определяется
директивой
конфигурационного файла php.ini. Директива, как правило, принимает значение, равное 8 Мбайт, и в данный объем должен уместиться как сам скрипт,
так и все переменные и массивы, с которыми он работает. Это означает, что
прочитать в переменную содержимое 10-мегабайтного файла уже не получится — просто не хватит памяти. В этом случае обычно используют функцию fgets(), которая позволяет читать файл построчно (строку можно перезатирать на каждой итерации или после обработки уничтожать при помощи
функции unset()).
170
Глава 5
Функция fgets() имеет аналог для чтения файлов с удалением из него тегов
HTML — fgetss(). Данная функция имеет следующий синтаксис:
string fgetss(int file, int length [, string allowable_tags])
Необязательный третий параметр allowable_tags может содержать строку со
списком тегов, которые не должны быть отброшены, при этом теги в строке
записываются через запятую.
Одно из назначений функции fgets() — это преобразование содержимого
файла в массив, в котором каждой строке соответствует отдельный элемент.
Создание такого массива демонстрируется в листинге 5.17.
Листинг 5.17. Извлечение содержимого файла в массив
<?php
// Имя файла
$filename = "file.txt";
// Открываем файл для чтения
$fd = fopen($filename, "r");
// Помещаем содержимое файла в массив $arr, каждому
// элементу которого соответствует отдельная строка
while (!feof ($fd))
{
$arr[] = fgets($fd, 4096);
}
// Закрываем файл
fclose($fd);
// Выводим дамп массива
echo "<pre>";
print_r($arr);
echo "</pre>";
?>
В результате выполнения скрипта из листинга 5.17 в массив $arr будут помещены строки из файла file.txt. Содержимое массива выводится в виде дампа при помощи функции print_r(), чтобы сохранить формат функции, вывод
обрамляется тегами <pre> и </pre>.
Работа с файлами и каталогами
171
Вообще, операция преобразования содержимого файла в массив является одной из самых распространенных, поэтому для нее была введена специальная
функция file(), которая имеет следующий синтаксис:
array file(string filename [, int use_include_path])
Функция считывает файл с именем filename и возвращает массив, каждый
элемент которого соответствует строке в прочитанном файле. В листинге 5.18 с
помощью функции читается файл, информация из которого затем выводится
в браузер.
Листинг 5.18. Использование функции file()
<?php
$content = file("file.txt");
foreach($content as line) echo "$line<br>";
?>
Эта функция удобна также тем, что с ее помощью можно легко подсчитать
количество строк в файле (листинг 5.19).
Листинг 5.19. Подсчет количества строк в файле
<?php
$content = file("file.txt");
echo count($content);
?>
Однако если размер файла слишком большой, чтобы результирующий массив
поместился в памяти, отводимой под скрипт, необходимо подсчитывать число строк при помощи функции fgets() (листинг 5.20).
Листинг 5.20. Альтернативный способ подсчета числа строк в файле
<?php
$fd = fopen("file.txt", "r");
// Устанавливаем счетчик строк в ноль
$counter = 0;
while (!feof ($fd))
{
172
Глава 5
fgets($fd, 4096);
// Увеличиваем значение счетчика после прочтения строки
$counter++;
}
// Закрываем файл
fclose($fd);
// Выводим значение счетчика
echo $counter;
?>
Помимо традиционных файловых функций, в состав PHP входят файловые
функции для чтения файлов специального формата. Для чтения файлов с расширением csv применяется функция fgetcsv(), которая имеет следующий
синтаксис:
array fgetcsv(int file, int length, char delim)
Функция читает строку из файла и разбивает ее по символу delim. Строка
delim должна состоять из одного символа, иначе принимается во внимание
только первый символ этой строки. Функция возвращает получившийся массив или false, если достигнут конец файла. Пустые строки в файле не игнорируются, а возвращаются как массив из одного элемента — пустой строки.
Параметр length задает максимальную длину строки точно так же, как это
делается в функции fgets().
ПРИМЕЧАНИЕ
CSV-файл (Comma Separated Value) — это текстовый файл, в котором данные
построены следующим образом: строки файла представляют собой строки таблицы, а столбцы таблицы разделены в файле заранее определенным символомразделителем, например, ;.
Кроме рассмотренных функций, в PHP имеется еще ряд функций, облегчающих ввод/вывод из файлов. При чтении данных из файла указатель текущей
позиции перемещается к очередному непрочитанному символу. Существует
несколько функций, с помощью которых можно управлять положением этого
указателя.
Установка указателя текущей позиции в начало файла производится функцией rewind():
int rewind(int fd)
Работа с файлами и каталогами
173
Аргумент fd является дескриптором файла, который возвращает функция
fopen(). Узнать текущее положение указателя можно при помощи функции
ftell():
int ftell(int fd)
Установить указатель в любое место файла можно, используя функцию
fseek():
int fseek(int fd, int offset [, int whence])
Функция fseek() устанавливает указатель файла на байт со смещением
offset (от начала файла, от его конца или от текущей позиции, в зависимости
от значения параметра whence). Аргумент fd представляет собой дескриптор
файла. Аргумент whence задает, с какого места отсчитывается смещение
offset, и может принимать одно из следующих значений:
ˆ SEEK_SET (отсчитывает позицию от начала файла);
ˆ SEEK_CUR (отсчитывает позицию относительно текущего положения указа-
теля);
ˆ SEEK_END (отсчитывает позицию относительно конца файла).
По умолчанию аргумент whence имеет значение SEEK_SET.
Узнать, находится ли указатель в конце файла, можно с помощью функции
feof():
int feof(int fd)
Если указатель находится в конце файла, функция возвращает true, в ином
случае возвращается false.
Содержимое открытого файла можно отобразить в браузере с помощью
функции fpassthru(), которая имеет следующий синтаксис:
int fpassthru (int fd)
Аргумент fd представляет собой дескриптор файла. В листинге 5.21 приведен
пример отображения двоичного файла pavlovo.jpg.
Листинг 5.21. Отображение двоичного файла
<?php
$fd = fopen("pavlovo.jpg", "rb");
if(!fd) exit("Ошибка открытия файла");
else fpassthru($fd);
?>
174
Глава 5
Для текстовых файлов существует еще одна функция отображения —
readfile():
readfile(string filename)
Функция принимает в качестве аргумента имя файла и выводит его содержимое в окно браузера (листинг 5.22).
Листинг 5.22. Отображение текстового файла
<?php
readfile("file.txt");
?>
Вывод случайной строки из файла
Пусть имеется текстовый файл text.txt, содержимое которого представлено
в листинге 5.23.
Листинг 5.23. Содержимое текстового файла text.txt
1 Программирование
2 Программирование на PHP
3 Программирование на JavaScript
4 Программирование на ASP.NET
5 Программирование на Java
6 Программирование на Perl
7 Программирование на C++
8 Программирование на Pascal
9 Программирование на Fortran
10 Программирование на Assembler
Создадим скрипт, который выводил бы одну случайную запись из файла. Такого рода скрипты часто используются для генерации фразы дня, вывода
анекдотов и афоризмов на сайте. Для решения данной задачи необходимо использование функции rand(), которая имеет следующий синтаксис:
int rand([int min, int max])
Функция генерирует случайное целое число между двумя целочисленными
параметрами min и max. Если необязательные параметры min и max не указаны, число будет расположено между 0 и RAND_MAX (которое равно 32 768).
Работа с файлами и каталогами
175
ЗАМЕЧАНИЕ
Традиционно генератор случайных чисел инициировался временной отметкой
вручную, в противном случае генератор всегда генерировал одну и ту же последовательность случайных цифр. В PHP эту задачу выполняла функция
srand(). Начиная с версии PHP 4.2, было принято решение делать это автоматически, и теперь в инициализации генератора нет необходимости, хотя это
не возбраняется и оставлено для обратной совместимости со старым кодом.
Решение поставленной выше задачи может выглядеть так, как это представлено в листинге 5.24.
Листинг 5.24. Случайный вывод из файла
<?php
// Имя файла
$filename = "text.txt";
// Помещаем содержимое файла count.txt
// в массив $lines
$lines = file($filename);
// Генерируем случайный индекс массива $lines
$index = rand(0, count($lines) - 1);
// Выводим строку номер $index
echo $lines[$index];
?>
Как видно из листинга 5.24, после того как содержимое файла разбивается на
строки и помещается в массив $lines, при помощи функции rand() вычисляется случайный элемент массива $index. Далее вывод элемента массива
с этим индексом не представляет сложности.
В реальной практике чаще требуется вывести не одно случайное значение,
а два, три или более. Особенно это актуально для задач, связанных с созданием панелей баннеров, когда число рекламных мест на сайте ограничено,
и требуется выводить баннеры в случайном порядке, но так, чтобы в каждый
момент времени в панели не попадались два одинаковых баннера. В этом
случае порядок вывода случайного числа изменяется: массив перемешивается
при помощи специальной функции shuffle(), которая имеет следующий
синтаксис:
void shuffle(array arr)
Функция принимает массив $arr, элементы которого перемешиваются в случайном порядке. Теперь, если потребуется вывести 5 случайных записей из
176
Глава 5
файла, достаточно перемешать массив и взять первые пять элементов (листинг 5.25).
Листинг 5.25. Случайный вывод из файла нескольких записей
<?php
// Имя файла
$filename = "text.txt";
// Помещаем содержимое файла count.txt
// в массив $lines
$lines = file($filename);
// Перемешиваем элементы массива $lines
shuffle($lines);
// Выводим первые пять элементов массива $lines
for($i = 0; $i < 5; $i++)
{
echo $lines[$i]."<br>";
}
?>
Результат работы скрипта из листинга 5.25 может выглядеть следующим образом:
6 Программирование на Perl
4 Программирование на ASP.NET
10 Программирование на Assembler
3 Программирование на JavaScript
5 Программирование на Java
Изменение порядка следования строк
в файле
Задачи преобразования текстовых файлов встречаются достаточно часто
в реальной практике. Изменим порядок следования строк в файле text.txt
(см. листинг 5.23) на обратный так, чтобы первая строка оказалась на последнем месте, вторая на предпоследнем, ..., последняя на первом.
Как и в предыдущем примере, здесь удобнее воспользоваться функцией
file(), которая возвращает массив со строками файла. После этого остается
только изменить порядок следования строк в массиве на обратный при помощи специальной функции array_reverse() и перезаписать содержимое
Работа с файлами и каталогами
177
файла, объединив полученный массив в одну строку при помощи функции
implode() (листинг 5.26).
Листинг 5.26. Изменение порядка следования строк в файле
<?php
// Разбиваем содержимое массива на отдельные строки
// при помощи функции file(), которая возвращает массив,
// каждый элемент которого содержит строку файла
$arr = file("text.txt");
// Формируем новый массив с обратным порядком
// следования элементов
$arr = array_reverse($arr);
// Перезаписываем содержимое файла text.txt
$fd = fopen("text.txt", "w");
if($fd)
{
fwrite($fd,implode("", $arr));
fclose($fd);
}
?>
Функция array_reverse() принимает в качестве аргумента массив, который
следует "перевернуть", но в отличие от функции shuffle(), рассмотренной
в предыдущем разделе, выполняет преобразование не над аргументом, а возвращает отдельный массив. Чтобы не плодить лишние массивы, в листинге 5.26
результат присваивается исходному массиву $arr.
Для того чтобы записать массив в файл, его предварительно необходимо
"превратить" в строку. Это можно осуществить при помощи функции
implode(), которая имеет следующий синтаксис:
string implode(string glue, array arr)
Функция принимает массив arr и разделитель glue, который используется
для того, чтобы разделять элементы массива в строки. В листинге 5.26 в качестве такого разделителя выступает пустая строка. Это связано с тем, что
функция file() не удаляет пробельные символы, в том числе и символ перевода строки. Правда, такое поведение может сыграть с нами злую шутку,
особенно, если последняя строка не завершается символом перевода строки.
В этом случае результат работы скрипта может выглядеть так, как это представлено в листинге 5.27.
178
Глава 5
Листинг 5.27. Искажение структуры файла text.txt
10 Программирование на Assembler9 Программирование на Fortran
8 Программирование на Pascal
7 Программирование на C++
6 Программирование на Perl
5 Программирование на Java
4 Программирование на ASP.NET
3 Программирование на JavaScript
2 Программирование на PHP
1 Программирование
Для того чтобы избежать такого поведения скрипта, можно принудительно
удалить ведущие и конечные пробелы строки при помощи функции trim().
Для того чтобы применить эту функцию к каждому элементу массива $arr,
обычно используют функцию array_walk(), которая позволяет применить
пользовательскую функцию к каждому элементу массива. Функция имеет
следующий синтаксис:
bool array_walk(array arr, callback function [, mixed userdata])
Функция принимает в качестве первого элемента преобразуемый массив
$arr, в качестве второго элемента function — имя функции, которая будет
осуществлять преобразование над каждым элементом массива, третий не обязательный параметр userdata позволяет передать дополнительный параметр
пользовательской функции function.
Для того чтобы удалить лишние пробельные символы (к которым относятся
как пробелы, так и переводы строк), создадим функцию обратного вызова
trim_array(), при помощи которой каждый элемент массива $arr будет обрабатываться функцией trim(). В результате код из листинга 5.26 может
быть преобразован в скрипт, представленный в листинге 5.28.
ЗАМЕЧАНИЕ
В различных операционных системах по-разному обозначаются переводы
строк. В UNIX-подобных операционных системах для обозначения перевода
строки используется последовательность \n, в Windows — \r\n, в Macintosh —
\n\r. Это приводит к тому, что файлы, созданные в одной операционной системе, могут не пониматься программами из другой операционной системы. Так
Блокнот в Windows понимает только перевод строки вида \r\n, а если встречает последовательность \n, выводит ее в виде квадратика (нечитаемого символа). Утилита mc в UNIX-подобных операционных системах наоборот воспринимает только UNIX-перевод строки \n, а символ возврата каретки \r
отображает как нечитаемый символ.
Работа с файлами и каталогами
179
Листинг 5.28. Универсальный скрипт изменения порядка следования строк
в файле
<?php
// Разбиваем содержимое массива на отдельные строки
// при помощи функции file(), возвращающей массив,
// каждый элемент которого содержит строку файла
$arr = file("text.txt");
// Удаляем все пробельные символы
// в конце строк
array_walk($arr, 'trim_array');
// Формируем новый массив с обратным порядком
// следования элементов
$arr = array_reverse($arr);
// Перезаписываем содержимое файла text.txt
$fd = fopen("text.txt", "w");
if($fd)
{
fwrite($fd,implode("\r\n", $arr));
fclose($fd);
}
// Пользовательская функция обратного вызова
function trim_array(&$item, $key)
{
$item = trim($item);
}
?>
Следует отметить, что в листинге 5.28 в качестве разделителя строк функции
implode() передается уже не пустая строка, а перевод строки \r\n. Если этого не сделать, все элементы массива сольются в одну строку.
Редактирование файла
При работе с текстовыми файлами часто возникает задача частичного изменения содержимого файла. Если требуется просто дописать новую строку,
проблем обычно не возникает — достаточно открыть файл при помощи
функции fopen() с атрибутом a, и любая новая строка будет просто дописана
в конец файла. Однако, если требуется добавить или отредактировать строку
180
Глава 5
в середине файла, потребуется прочитать содержимое файла, разобрать, отредактировать нужный фрагмент и переписать полностью содержимое. Рассмотрим редактирование отдельной строки файла на примере текстового
файла text.txt (см. листинг 5.23). Пусть требуется заменить строку, помеченную индексом 7, на "Программирование на C/C++". С такой задачей может
справиться скрипт, представленный в листинге 5.29.
Листинг 5.29. Редактирование файла
<?php
// Ищем строку с индексом 7
$index = 7;
// Имя файла
$filename = "text.txt";
// Читаем содержимое файла в строку $bufet
$bufer = file_get_contents($filename);
// Находим строку с индексом $index
// и заменяем строку
$bufer = preg_replace("|$index ([^\n]*)|",
"$index Программирование на C/C++",
$bufer);
// Сохраняем результат в файле
$fd = fopen($filename, "w");
fwrite($fd, $bufer);
fclose($fd);
?>
ЗАМЕЧАНИЕ
Подробное рассмотрение синтаксиса регулярных выражений выходит за рамки
нашей книги, вы можете ознакомиться с ним в книге Дж. Фридла "Регулярные выражения" или в случае возникновения вопросов обратиться на наш форум, посвященный
регулярным
выражениям
http://www.softtime.ru/forum/index.php?id_forum=6.
Для замены строки используются так называемые регулярные выражения
(шаблоны), передаваемые функции замены по регулярному выражению
preg_replace(), которая имеет следующий синтаксис:
mixed preg_replace (mixed pattern, mixed replacement, mixed subject
[, int limit])
Эта функция ищет в строке subject соответствие регулярному выражению
pattern и заменяет его на replacement. Необязательный параметр limit за-
Работа с файлами и каталогами
181
дает число соответствий, которые необходимо заменить. Если этот параметр
не указан или равен –1, то заменяются все найденные соответствия.
Параметр replacement может содержать ссылки вида \\n. Каждая такая
ссылка будет заменена на подстроку, соответствующую n-ым круглым скобкам. n может принимать значения от 0 до 99, причем ссылка \\0 соответствует вхождению всего шаблона. Выражения в круглых скобках нумеруются
слева направо, начиная с единицы.
Если во время выполнения функции были обнаружены совпадения с шаблоном, будет возвращено измененное значение subject, в противном случае
будет возвращен исходный текст subject.
После того как замена в строке $bufer произведена, файл text.txt открывается
при помощи функции fopen() с атрибутом w, а его содержимое перезаписывается.
Однако такой скрипт не очень функционален. Чаще при редактировании текстовых файлов (и информации в базе данных) создается Web-интерфейс, который упрощает использование скрипта. Создадим такой интерфейс для
скрипта из листинга 5.29. В первую очередь создадим HTML-форму, позволяющую посетителю вводить номер строки. После отправки данных форма
должна выводить содержимое строки в текстовую область, после редактирования информации в которой посетитель получает возможность сохранить
данные в файл (рис. 5.3).
Рис. 5.3. Редактирование строки файла через Web-интерфейс
Реализация Web-интерфейса, изображенного на рис. 5.3, может быть такой,
как это представлено в листинге 5.30.
Листинг 5.30. Web-интерфейс для редактирования содержимого файла
182
<?php
// Имя файла
$filename = "text.txt";
// Обработчик HTML-формы
if(!empty($_POST['number']) && !empty($_POST['new_text']))
{
// Читаем содержимое файла
$arr = file("text.txt");
// Проверяем, имеется ли в файле строка с номером
// $_POST['number']
if($_POST['number'] > 0 && $_POST['number'] <= count($arr))
{
// Читаем содержимое файла в строку $bufet
$bufer = file_get_contents($filename);
// Находим строку с индексом $index
// и заменяем строку
$bufer = preg_replace("|$_POST[number] ([^\n]*)|",
$_POST['new_text'],
$bufer);
// Сохраняем результат в файле
$fd = fopen($filename, "w");
fwrite($fd, $bufer);
fclose($fd);
}
// Перезагружаем страницу, сбрасывая POST-данные
header("Location: $_SERVER[PHP_SELF]");
exit();
}
?>
<form method=post>
<input size=40 type=text name=number
value="<?= $_POST['number']; ?>"><br>
<?php
// Если передан номер строки, загружаем строку в форму
if(!empty($_POST['number']))
{
// Читаем содержимое файла
$arr = file($filename);
// Проверяем, имеется ли в файле строка с номером
Глава 5
Работа с файлами и каталогами
183
// $_POST['number']
if($_POST['number'] > 0 && $_POST['number'] <= count($arr))
{
echo "<input size=40 type=text name=new_text value='".
$arr[$_POST['number'] - 1]."'><br>";
}
}
?>
<input type=submit value="Отправить">
</form>
Как видно из листинга 5.30, HTML-формы формируются динамически, т. е.
текстовое поле с редактируемой строкой подключается только после того, как
клиент вводит число в первое поле. Так как и HTML-форма, и ее обработчик
находятся в одном файле, сразу после того, как информация в файле обновлена, необходимо перезагрузить страницу, отправив HTTP-заголовок
Location с именем текущего файла. Это сбрасывает POST-данные, и повторная перезагрузка страницы не будет приводить к срабатыванию обработчика
HTML-формы.
Сортировка текстового файла
Информация в текстовом файле не всегда может находиться в порядке, удобном
для восприятия и дальнейшей обработки. Создадим скрипт, который будет перемешивать записи в файле text.txt (см. листинг 5.23) и сортировать его снова.
Перемешать записи файла в случайном порядке можно, например, следующим образом: извлечь строки текстового файла text.txt при помощи функции
file(), а затем перемешать содержимое файла при помощи функции
shuffle(), синтаксис которой рассмотрен ранее. PHP-скрипт, осуществляющий эту операцию, может выглядеть так, как это представлено в листинге
5.31.
Листинг 5.31. Создание случайного следования записей в файле
<?php
// Имя файла
$filename = "text.txt";
// Читаем содержимое файла
$lines = file($filename);
// Перемешиваем записи случайным образом
184
Глава 5
shuffle($lines);
// Удаляем все пробельные символы
// в конце строк
array_walk($lines, 'trim_array');
// Сохраняем результат в файле
$fd = fopen($filename, "w");
fwrite($fd, implode("\r\n", $lines));
fclose($fd);
function trim_array(&$item, $key)
{
$item = trim($item);
}
?>
При
помощи функции array_walk() и функции обратного вызова
каждый элемент промежуточного массива $lines пропускается
через функцию trim(), которая удаляет возможные пробельные символы
в начале и конце строки, в том числе и символы перевода строки. Результат
работы скрипта из листинга 5.31 может выглядеть так, как это представлено в
листинге 5.32.
trim_array()
Листинг 5.32. Записи файла text.txt в случайном порядке
8 Программирование на Pascal
4 Программирование на ASP.NET
7 Программирование на C++
10 Программирование на Assembler
3 Программирование на JavaScript
5 Программирование на Java
1 Программирование
6 Программирование на Perl
2 Программирование на PHP
9 Программирование на Fortran
Для сортировки полученного файла, достаточно разбить его содержимое на
строки при помощи функции file() и отсортировать полученный массив
стандартной функцией sort() или rsort()в прямом или обратном порядке
(листинг 5.33).
Работа с файлами и каталогами
185
Листинг 5.33. Сортировка записей файла text.txt по индексу
<?php
// Имя файла
$filename = "text.txt";
// Читаем содержимое файла
$lines = file($filename);
// Сортируем записи по индексу
sort($lines);
// Удаляем все пробельные символы
// в конце строк
array_walk($lines, 'trim_array');
// Сохраняем результат в файле
$fd = fopen($filename, "w");
fwrite($fd, implode("\r\n", $lines));
fclose($fd);
function trim_array(&$item, $key)
{
$item = trim($item);
}
?>
В результате работы скрипта из листинга 5.33 файл text.txt может выглядеть
так, как это представлено в листинге 5.34.
Листинг 5.34. Сортировка строк в файле text.txt
1 Программирование
10 Программирование на Assembler
3 Программирование на JavaScript
2 Программирование на PHP
4 Программирование на ASP.NET
5 Программирование на Java
6 Программирование на Perl
7 Программирование на C++
8 Программирование на Pascal
9 Программирование на Fortran
186
Глава 5
Однако результат показывает, что сортировка производится не совсем привычным образом — индекс 10 сразу следует за индексом 1. Это связано
с тем, что записи сортируются как строки, а не как числа (листинг 5.34).
Для того чтобы обойти это ограничение, необходимо воспользоваться функцией natsort(), которая проводит "естественную" сортировку (листинг 5.35).
Листинг 5.35. "Естественная" сортировка файла text.txt
<?php
// Имя файла
$filename = "text.txt";
// Читаем содержимое файла
$lines = file($filename);
// Сортируем записи по индексу
natsort($lines);
// Удаляем все пробельные символы
// в конце строк
array_walk($lines, 'trim_array');
// Сохраняем результат в файле
$fd = fopen($filename, "w");
fwrite($fd, implode("\r\n", $lines));
fclose($fd);
function trim_array(&$item, $key)
{
$item = trim($item);
}
?>
Результат "естественной" сортировки представлен в листинге 5.36.
Листинг 5.36. Результат "естественной" сортировки файла text.txt
1
2
3
4
5
6
Программирование
Программирование
Программирование
Программирование
Программирование
Программирование
на
на
на
на
на
PHP
JavaScript
ASP.NET
Java
Perl
Работа с файлами и каталогами
187
7 Программирование на C++
8 Программирование на Pascal
9 Программирование на Fortran
10 Программирование на Assembler
Для сортировки по текстовой информации, следующей за индексом, необходимо преобразовать содержимое файла text.txt в массив, индексом которого служил бы номер записи, а значением элемента — текстовая строка. После чего
можно воспользоваться функцией сортировки asort(), которая сохраняет отношение между индексом и содержимым при сортировке (листинг 5.37).
Листинг 5.37. Сортировка по текстовому содержимому
<?php
// Имя файла
$filename = "text.txt";
// Читаем содержимое файла
$lines = file($filename);
// Сортируем массив
asort($temp);
// Удаляем все пробельные символы
// в конце строк
array_walk($lines, 'trim_array');
// Формируем конечный массив
foreach($temp as $key => $value)
{
$line[] = $key." ".$value;
}
// Сохраняем результат в файле
$fd = fopen($filename, "w");
fwrite($fd, implode("\r\n", $line));
fclose($fd);
?>
Строка файла ищется при помощи регулярного выражения #([\d]+)
([^\n]+)(\n|$)#U. Первые круглые скобки соответствуют числу, вторые
круглые скобки соответствуют любой последовательности символов кроме
символа перевода строки \n. Последние круглые скобки нужны для поиска
либо символу перевода строки, либо окончанию строки $. Символ $ необхо-
Download