13

The closest I've seen in the PHP docs, is to fread() a given length, but that doesnt specify which line to start from. Any other suggestions?

Xenph Yan
  • 76,635
  • 15
  • 45
  • 54
lock
  • 5,502
  • 16
  • 56
  • 75

7 Answers7

35

Yes, you can do that easily with SplFileObject::seek

$file = new SplFileObject('filename.txt');
$file->seek(1000);
for($i = 0; !$file->eof() && $i < 1000; $i++) {
    echo $file->current(); 
    $file->next();
}

This is a method from the SeekableIterator interface and not to be confused with fseek.

And because SplFileObject is iterable you can do it even easier with a LimitIterator:

$file = new SplFileObject('longFile.txt');
$fileIterator = new LimitIterator($file, 1000, 2000);
foreach($fileIterator as $line) {
    echo $line, PHP_EOL;
}

Again, this is zero-based, so it's line 1001 to 2001.

Gordon
  • 296,205
  • 68
  • 508
  • 534
  • 2
    +1 SPL is very nice and could use more advertisement (and documentation) – Matteo Riva May 11 '10 at 09:00
  • 4
    Just remember that the SPL implementation is implemented in the same manner as the first suggested solution. It will start reading the file from the first byte, one line at a time, and leave the file pointer at the desired line. There is no way to get around that issue. – MatsLindh May 11 '10 at 11:20
13

You not going to be able to read starting from line X because lines can be of arbitrary length. So you will have to read from the start counting the number of lines read to get to line X. For example:

<?php
$f = fopen('sample.txt', 'r');
$lineNo = 0;
$startLine = 3;
$endLine = 6;
while ($line = fgets($f)) {
    $lineNo++;
    if ($lineNo >= $startLine) {
        echo $line;
    }
    if ($lineNo == $endLine) {
        break;
    }
}
fclose($f);
grom
  • 14,812
  • 18
  • 60
  • 65
  • Or you could just use file() and array_slice(), as in my answer :) – Factor Mystic Feb 05 '09 at 05:45
  • 1
    Yeah except that reads the whole file. This code only reads the minimum required. – grom Feb 05 '09 at 05:51
  • will that work even the file is so huge like the one i currently need to work on is 25MB? – lock Feb 05 '09 at 05:53
  • @lock, yes it should. The example I gave only ever has one line in memory. You can store the lines into an array as long as don't have too many. Having said that 25Mb is not huge compared to some log files I have had to process. – grom Feb 05 '09 at 06:33
  • This function is ridiculously inefficient with extremely large files. If I wanted to get the last 100 lines of a 1 000 000 line file, the loop would run 1 000 000 times. – paullb Jun 27 '12 at 14:46
  • @paullb Yes but lines don't have a fixed length so no way around it. But if you want the last 100 lines you can start from end of file and read backwards. – grom Jul 13 '12 at 06:42
  • The other answer using the LimitIterator works perfectly. (It should be the best answer. It's not as of 2012-07-19) – paullb Jul 19 '12 at 08:35
  • @paullb My answer works perfectly too, not sure what you are implying. But I agree with you, the SPL version is more modern and cleaner. However it has the same inefficiency that you are complaining about in that it has to scan the previous lines. – grom Jul 24 '12 at 00:47
  • 1
    If you are running into memory problems with long running scripts and lower versions of PHP it is best to avoid objects as much as possible which is why I prefer the answer by @grom – im3r3k Nov 26 '13 at 21:23
3

Here is the possible solution :)

<?php
$f = fopen('sample.txt', 'r');
$lineNo = 0;
$startLine = 3;
$endLine = 6;
while ($line = fgets($f)) {
    $lineNo++;
    if ($lineNo >= $startLine) {
        echo $line;
    }
    if ($lineNo == $endLine) {
        break;
    }
}
fclose($f);
?>
Community
  • 1
  • 1
Sarfraz
  • 355,543
  • 70
  • 511
  • 562
  • 1
    But it reads all lines before X. The question is asking if this part can be skipped, isn't it? – Martin Vseticka May 11 '10 at 06:56
  • See the `$startLine` and `$endLine` variables, it will read only those lines. – Sarfraz May 11 '10 at 06:57
  • 1
    The question is not about processing only given lines (>=startLine && <= endLine). It's about minimalizing the number of reading operation on disk. – Martin Vseticka May 11 '10 at 07:16
  • It's a nice solution, but still time consuming. The deeper you go into a file the longer it will take... And I intend to work with files that have 10,000+ lines. – thedp May 11 '10 at 07:22
  • @thedp: let's see if there is a better solution :) – Sarfraz May 11 '10 at 07:25
  • @Sarfraz: What do you think of my so called "plan B" solution? http://stackoverflow.com/questions/2808583/read-a-file-from-line-x-to-line-y/2808707#2808707 – thedp May 11 '10 at 08:25
  • @Sarfraz: BTW, nice code reuse - http://stackoverflow.com/questions/514673/how-do-i-open-a-file-from-line-x-to-line-y-in-php/514717#514717 – thedp May 11 '10 at 12:10
  • @thedp: in the my asnwer, the first link points to that source, i posted it here for some users based on their comments so that things were clarified. – Sarfraz May 11 '10 at 12:33
3

Well, you can't use function fseek to seek the appropriate position because it works with given number of bytes.

I think that it's not possible without some sort of cache or going through lines one after the other.

Martin Vseticka
  • 25,160
  • 25
  • 118
  • 187
  • 4
    What about a line cache? Store somewhere the byte positions of each line, then use fseek() to get to them. – Christian Studer May 11 '10 at 06:57
  • @christian studer: You're talking about Indexing a file? This could be an interesting solution if the file was static and wouldn't change most of the time. Unfortunately, the files I'm going to read are source-code files still in development, so indexing is out of the question. – thedp May 11 '10 at 08:31
  • 1
    Yes, indexing it and cache the index. (The timestamp on the file might give an indication if the cached index is still valid). PHP is surprisingly fast for these tasks, and if you have several requests for the same file before it changes again, might be fast enough to pull this off. – Christian Studer May 11 '10 at 09:04
3

Unfortunately, in order to be able to read from line x to line y, you'd need to be able to detect line breaks... and you'd have to scan through the whole file. However, assuming you're not asking about this for performance reasons, you can get lines x to y with the following:

$x = 10; //inclusive start line
$y = 20; //inclusive end line
$lines = file('myfile.txt');
$my_important_lines = array_slice($lines, $x, $y);

See: array_slice

Factor Mystic
  • 24,253
  • 15
  • 78
  • 91
  • You should note that the array starts with 0 and line numbering usually starts with 1. So there should be a $x-1, $y-1 or remember $x=10 is *really* $x=11. – null Feb 05 '09 at 16:22
0

I was afraid of that... I guess it's plan B then :S

For each AJAX request I'm going to:

  1. Read into a string the number of lines I'm going to return to the client.
  2. Copy the rest of the file into a temp file.
  3. Return string to the client.

It's lame, and it will probably be pretty slow with 10,000+ lines files, but I guess it's better than reading the same over and over again, at least the temp file is getting shorter with every request... No?

thedp
  • 8,178
  • 16
  • 49
  • 87
0

If you're looking for lines then you can't use fread because that relies on a byte offset, not the number of line breaks. You actually have to read the file to find the line breaks, so a different function is more appropriate. fgets will read the file line-by-line. Throw that in a loop and capture only the lines you want.

Andrew Vit
  • 18,433
  • 6
  • 73
  • 83