如何使用PHP从文本文件开头删除X行?

7
我正在编写一个PHP脚本,该脚本输出操作的简单文本文件日志。当达到某个文件大小时,如何使用PHP删除此文件的前几行?
理想情况下,我希望保留前两行(日期/时间创建和空白行),从第三行开始删除X行。我已经知道了filesize()函数,所以我将使用它来检查文件大小。
示例日志文本:
*** LOG FILE CREATED ON 2008-10-18 AT 03:06:29 ***

2008-10-18 @ 03:06:29  CREATED: gallery/thumbs
2008-10-18 @ 03:08:03  RENAMED: gallery/IMG_9423.JPG to gallery/IMG_9423.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/IMG_9188.JPG to gallery/IMG_9188.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/IMG_9236.JPG to gallery/IMG_9236.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/IMG_9228.JPG to gallery/IMG_9228.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/IMG_3104.JPG to gallery/IMG_3104.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/First dance02.JPG to gallery/First dance02.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/BandG02.JPG to gallery/BandG02.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/official03.JPG to gallery/official03.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/Wedding32.JPG to gallery/Wedding32.jpg
2008-10-18 @ 03:08:03  RENAMED: gallery/Gettaway car16.JPG to gallery/Gettaway car16.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/Afterparty05.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/IMG_9254.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/IMG_9175.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/official05.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/First dance01.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/Wedding29.jpg
2008-10-18 @ 03:08:04  CREATED: gallery/thumbs/men walking.jpg
9个回答

22

使用SPL吧,卢克

PHP 5自带了许多好用的迭代器:

<?php

$line_to_strip = 5;
$new_file = new SplFileObject('test2.log', 'w');

foreach (new LimitIterator(new SplFileObject('test.log'), $line_to_strip) as $line)
    $new_file->fwrite($line);    

?>

相比于使用fopen时可能出现的混乱情况,它更加清晰易懂。它不会把整个文件放在内存中,每次只读取一行内容。而且由于它是完全面向对象的,你可以轻松地将它插入到任何需要的地方并重用代码模式。


6
这个解决方案会写入一个新文件,但问题是要求编辑一个已有的文件。 - ummdorian

4
这是一个关于日志文件的教科书式问题,我想提出另一种解决方案。
“删除文件开头的行”的方法存在一个问题,即一旦它必须为每个新行删除第一行,添加新行变得极其缓慢。
正常的日志文件附加只涉及在文件系统中在文件末尾写入几个字节(偶尔需要分配一个新扇区,这会导致广泛的碎片化-为什么日志文件通常是这样)。
但是,在你每写一行时都要删除开头的一行时,这里的大问题就出现了。整个文件必须首先读入内存,然后重新写入,从而导致对硬盘的大量I/O操作(相对而言)。更糟糕的是,这里的“将其拆分为PHP数组并跳过第一行”解决方案由于PHP数组的性质而极其缓慢。如果日志文件大小限制非常小或不经常写入,则不会出现此问题,但是对于大量写入(如日志文件的情况),必须执行同样的巨大操作很多次,从而导致主要性能障碍。
这可以想象为将汽车停放在有50个空间的一条线上。快速地停放前50辆车,只需在前面的车后面驶入即可。但是当您到达50时,并且必须删除文件的第一行时,您必须将第二辆车驶入第一位置,第三辆车驶入第二位置,依此类推,然后才能将最后一辆车停放在第50个位置。(而这必须为每个新车重复进行!)
我的建议是改为按日期保存到不同的日志文件中,然后存储最多30天等。因此利用了文件系统已经完美解决了这个问题。

很棒的想法,但对于我的特定需求来说,这是不可接受的。我有一个小的日志文件,可能不会经常写入。但我会记住这个想法,留待将来的项目使用。 - PHLAK

4
$x_amount_of_lines = 30;
$log = 'path/to/log.txt';
if (filesize($log) >= $max_size)) {
  $file = file($log);
  $line = $file[0];
  $file = array_splice($file, 2, $x_amount_of_lines);
  $file = array_splice($file, 0, 0, array($line, "\n")); // put the first line back in
  ...
}

编辑: 由rcar更正并保存第一行。


你可能想要使用 $max_size 而不是 ==。 - Randy

2
您可以使用file()函数将文件读入行数组,然后使用array_slice()删除前X行。
$X = 100; // Number of lines to remove

$lines = file('log.txt');
$first_line = $lines[0];
$lines = array_slice($lines, $X + 2);
$lines = array_merge(array($first_line, "\n"), $lines);

// Write to file
$file = fopen('log.txt', 'w');
fwrite($file, implode('', $lines));
fclose($file);

1
这是一个可用的函数。
<?php
//--------------------------------
// FUNCTION TO TRUNCATE LOG FILES
//--------------------------------
function trim_log_to_length($path,$numHeaderRows,$numRowsToKeep){
    $file = file($path);
    $headerRows = array_slice($file,0,$numHeaderRows);
    // if this file is long enough were we should be truncating it
    if(count($file) - $numRowsToKeep > $numHeaderRows){
        // figure out the rows we wanna keep
        $dataRowsToKeep = array_slice($file,count($file)-$numRowsToKeep,$numRowsToKeep);
        // write the file
        $newFileRows = array_merge($headerRows,$dataRowsToKeep);
        file_put_contents($path, implode($newFileRows));
    }
}
?>

0

以下代码将帮助您从文件开头删除指定行数的内容

$content = file('file.txt');
array_splice($content, 0, 5); // this line will delete first 5 lines //change asper your requirement  
file_put_contents('file.txt', $content);

0

如果您可以运行Linux命令,请尝试使用split。它允许您按行数分割以使事情变得更加容易。

否则,我想您将不得不将其读入并写入另外两个文件。


0

典型的操作系统不提供“原地”插入或删除文件内容的功能。您需要编写一个函数,读取第一个文件,并创建一个输出文件,其中包含您想要保留的行。然后,当您完成时,删除旧文件并将新文件重命名为旧名称。

伪代码如下:

open original file IN for reading
create new output file OUT
read the first two lines from IN
write these lines to OUT
for each line to skip:
    read a line from IN
for the remainder of the file:
    read a line from IN
    write the line to OUT
close IN
close OUT
delete IN
rename OUT to IN

这种方法相比其他一些方法的优点在于,它不需要您首先将整个文件读入内存。您没有提及上限大小是多少,但如果它类似于100 MB,您可能会发现将文件加载到内存中不是一个可接受的空间使用方式。

0

除了@Greg's的答案,您还可以将整个文件读入数组中,跳过前X个条目,然后将数组重写到文件中。

作为一种方法:http://us3.php.net/manual/en/function.file-get-contents.php

$fle = file_get_contents("filename");
// skip X many newlines, overwriting the contents of the string with ""
// http://us3.php.net/manual/en/function.file-put-contents.php
file_put_contents("filename", $fle);

问题在于,根据编写的行数不同,日期/时间将会错乱。此外,我预见文件将一遍又一遍地写入相同的行。 - PHLAK
是的,这可能是一个问题(重复写入相同的数据),这取决于文件的大小 - 这肯定是一种蛮力方法 :) 为什么日期/时间会不按顺序呢?除非在php读取文件时附加了文件,否则我认为您不会看到这种情况。 - warren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接