Код ниже открывает файловый дескриптор ($fp
) для определенного файла, затем я перезаписываю этот файл (используя rename
), а затем читаю содержимое этого файла с помощью fread
. Что меня поражает, так это то, что при чтении файла с помощью fread
он по-прежнему указывает на содержимое исходного файла (до перезаписи)! Как это произошло? Я думаю, что это возможно только в том случае, если fopen
сделает полную копию файла, чтобы его можно было прочитать позже, но я не могу поверить, что fopen
делает полную копию файла, потому что это было бы очень неэффективно.
<?php
$fileA = "fileA.txt";
$fileB = "fileB.txt";
file_put_contents($fileA,"aaaaaaaaaaaaaa111111",LOCK_EX);
file_put_contents($fileB,"bbbbbbbbbbbbbb222222",LOCK_EX);
$fp = fopen($fileA,"r");
rename($fileB,$fileA);
echo fread($fp,10000);
?>
Удивительно, но приведенный выше код выводит aaaaaaaaaaaaaa111111
, но должен выводить bbbbbbbbbbbbbb222222
. Если раньше fread
я использую fclose
и снова открываю файл, он работает как положено (отображается новый контент).
Мой вопрос в том, как PHP все еще может отображать содержимое исходного файла, который уже был перезаписан! Не могу поверить, что PHP сделал полную копию файла, когда я позвонил fopen
.
Приведенный выше код отлично работает в Linux, однако в Windows он не будет работать, поскольку использование rename
в файле, уже открытом с помощью fopen
, приведет к ошибке - в Linux все в порядке, ошибок нет.
fopen вернет поток, который будет читать содержимое файла. При переименовании файла место, где находится содержимое, не меняется. Файловая система Linux работает следующим образом: каждый файловый блок по существу указывает на то, где находится следующий блок (я упрощаю), поэтому, как только у вас будет открыт поток в начале файла, вы сможете прочитать весь файл.
@KenLee php.net/manual/en/function.rename.php говорит: «Если переименовать файл и он существует, он будет перезаписан».
@brombeer Они имеют в виду, что имя файла будет заменено ссылкой на переименовываемый файл, а не то, что содержимое файла будет скопировано поверх него. Я признаю, что это неудачно сформулировано.
@Barmar Это ответ на (теперь удаленный) комментарий Кенли, в котором он спрашивает что-то вроде: «Если файл A уже существует, можете ли вы даже переименовать файл B в файл A»
@HonkderHase Мне не нужно проверять значение, потому что значение действительно было переименовано, это не ошибка. Кроме того, меня беспокоит то, что для очень конфиденциального файла, когда я использую rename
, я ожидаю, что его содержимое «уйдет», однако по какой-то причине PHP сохраняет старое содержимое файла очень живым и доступным. Я понимаю, что некоторые из вас сказали здесь о том, что ссылка не удаляется, поэтому, если у меня есть ссылка на файл, я могу продолжать обращаться к нему в будущем, даже если файл был переименован ранее?
Для меня это звучит немного странно. Если вы переименуете файл, его содержимое не исчезнет. Любой, кто знает новое имя (или местонахождение данных на диске), по-прежнему сможет получить к ним доступ. Когда вы переименовываете файл, нет «старого файла» и «нового файла», это все тот же файл. В общих чертах, файловая система имеет своего рода «базу данных» (опять же, упрощая) имен файлов и указателей на то, где находятся (или начинаются) их данные. Когда вы переименовываете файл, вы просто меняете имя файла в этой базе данных, больше ничего не меняется. Все данные по-прежнему находятся в том же месте на диске.
Обратите внимание, что код (для вашего случая) в некоторой степени зависит от ОС.
Например, если мы немного изменим оператор переименования, чтобы отобразить результат функции переименования (например, как предложил комментатор):
echo "checking result of rename:" . rename($fileB,$fileA);
создание кода, чтобы стать:
<?php
$fileA = "fileA.txt";
$fileB = "fileB.txt";
file_put_contents($fileA,"aaaaaaaaaaaaaa111111",LOCK_EX);
file_put_contents($fileB,"bbbbbbbbbbbbbb222222",LOCK_EX);
$fp = fopen($fileA,"r");
echo "checking result of rename:" . rename($fileB,$fileA);
echo "<br>";
echo fread($fp,10000);
?>
тогда при запуске PHP-скрипта в ОС Windows результат (скажем, при запуске на XAMPP) будет:
Для того же кода, если запустить тот же PHP-скрипт в ОС Linux, результат будет:
Таким образом, для сценария PHP, работающего в ОС Win, поскольку ОС запрещает операцию переименования (когда файл «открыт»), операция перезаписи файла не выполняется при запуске функции переименования PHP, и, следовательно, система точно отображает содержимое исходный файл A.txt, который имеет вид «aaaaaaaaaaaaa111111». (да, я проверил, операция переименования не удалась, и там все еще есть два файла TXT)
Для сценария PHP, работающего в ОС Linux, операция переименования завершится успешно (даже если файл A.txt «открыт» с помощью fopen), поэтому PHP перезаписывает исходный файл A.txt содержимым файла B.txt с данными «bbbbbbbbbbbb222222». ".
Однако то, что вы наблюдаете, по-прежнему верно, потому что даже для PHP, работающего в ОС Linux, система будет отображать «aaaaaaaaaaaaaaaaaaaa111111», когда файл A.txt уже содержит данные «bbbbbbbbbbbbbb222222» после операции переименования.
Я проверил - это не имеет никакого отношения к тому, есть ли какая-либо задержка ввода-вывода в операции переименования перед операцией fread. Таким образом, даже если я добавлю строку «sleep(10)» перед оператором fread, система все равно будет отображать данные «aaaaaaaaaaaaaa111111», даже файл fileA.txt исчезнет и будет заменен на fileB.txt.
Таким образом, в этом случае дескриптор файла (в Linux) по-прежнему будет указывать на исходные данные файла, даже если вы используете функцию переименования для «перезаписи» данных исходного файла. Операция «перезаписи» (как указано в официальной документации PHP) сообщает вам результат операции. НО на самом деле дескриптор файла по-прежнему указывает на исходный поток данных (на вводе-выводе), поэтому система по-прежнему отображает старые данные.
Конечно, одним из способов решения проблемы будет обновление дескриптора файла, например:
Итак, для PHP, работающего в ОС Linux, подойдет следующее: (отобразится «bbbbbbbbbbbbbb222222»)
<?php
$fileA = "fileA.txt";
$fileB = "fileB.txt";
file_put_contents($fileA,"aaaaaaaaaaaaaa111111",LOCK_EX);
file_put_contents($fileB,"bbbbbbbbbbbbbb222222",LOCK_EX);
$fp = fopen($fileA,"r");
echo "checking result of rename:" . rename($fileB,$fileA);
echo "<br>";
fclose($fp);
$fp = fopen($fileA,"r");
echo fread($fp,10000);
?>
Это довольно хорошее расследование, и я могу подтвердить оба варианта поведения, о которых вы сообщили: в Windows операция переименования завершается неудачно, но в Linux она завершается успешно. Кроме того, ваше предложение закрыть обработчик перед чтением файла заставляет все работать так, как ожидалось. Меня забеспокоило это странное поведение, потому что, когда я использую rename
для переименования файла с очень конфиденциальной информацией, я очень надеюсь, что он исчез навсегда! Но, как вы доказали, если у другого процесса открыт этот старый файл (с помощью fopen
), этот процесс может получить доступ ко всей конфиденциальной информации из старого файла, пока обработчик остается открытым. Опасный
Файл открыт, функция rename() не удалась. Вы не проверили возвращаемое значение.