44

I need to read the content of a single file, "test.txt", inside of a zip file. The whole zip file is a very large file (2gb) and contains a lot of files (10,000,000), and as such extracting the whole thing is not a viable solution for me. How can I read a single file?

Alex Turpin
  • 46,743
  • 23
  • 113
  • 145
e-info128
  • 3,727
  • 10
  • 40
  • 57
  • 12
    This question is not actually stupid. I found myself here while googling for 'extract one file from zip php'. It is strange it has votes down. – ivkremer Feb 02 '14 at 01:09

2 Answers2

63

Try using the zip:// wrapper:

$handle = fopen('zip://test.zip#test.txt', 'r'); 
$result = '';
while (!feof($handle)) {
  $result .= fread($handle, 8192);
}
fclose($handle);
echo $result;

You can use file_get_contents too:

$result = file_get_contents('zip://test.zip#test.txt');
echo $result;
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • how to tell if the file exists? if(!@$test = file_get_contents(... ? – e-info128 May 02 '12 at 19:29
  • @user1243068: I'm not sure. That should work. You can use `is_file` to check if the `.zip` exists. As for the file inside the zip, I'm not sure. – gen_Eric May 02 '12 at 19:31
  • 2
    It also works with file, like $content=file('zip://test.zip#test.txt'); – Emil Borconi Jan 15 '16 at 12:05
  • I have `.tar.gz` file instead of `.zip` file, how to use the `file_get_contents('zip://test.zip#test.txt');`? – Sami Jul 12 '16 at 21:18
  • 3
    @Sami: The [`phar://` wrapper](http://php.net/manual/en/phar.using.stream.php) seems to support this. `file_get_contents('phar://yourfile.tar.gz/path/to/file.txt');` See: http://stackoverflow.com/a/4878849 – gen_Eric Jul 12 '16 at 21:23
  • @RocketHazmat, Thanks for your response. I tried to use phar:// wrapper, but my browser loading forever and does not echo the content. I have a `.tar.gz` file with 1,000,000 `.json` files. Any idea? – Sami Jul 12 '16 at 21:35
  • @Sami: What do you mean by "crashes"? Does it actually *crash* or does the script just stop running? Any errors or anything? – gen_Eric Jul 12 '16 at 21:36
  • @RocketHazmat, it loads forever :). Also, I checked the file using `tar -tf i.tar.gz` and there is no problem with the filepath. – Sami Jul 12 '16 at 21:40
  • I wonder if it's just too big of a file or something. I'm not 100% sure what could be going on. – gen_Eric Jul 12 '16 at 21:41
  • 1
    @RocketHazmat - have any idea how to do this with a remote https file? – Scott Paterson Mar 11 '17 at 22:10
  • @ScottPaterson: I didn't test this, but would `file_get_contents('zip://https://example.com/test.zip#test.txt');` work? Otherwise, you'd probably need to download the file into memory (or a temp folder) first. – gen_Eric Mar 13 '17 at 14:07
4

Please note @Rocket-Hazmat fopen solution may cause an infinite loop if a zip file is protected with a password, since fopen will fail and feof fails to return true.

You may want to change it to

$handle = fopen('zip://file.zip#file.txt', 'r');
$result = '';
if ($handle) {
    while (!feof($handle)) {
        $result .= fread($handle, 8192);
    }
    fclose($handle);
}
echo $result;

This solves the infinite loop issue, but if your zip file is protected with a password then you may see something like

Warning: file_get_contents(zip://file.zip#file.txt): failed to open stream: operation failed

There's a solution however

As of PHP 7.2 support for encrypted archives was added.

So you can do it this way for both file_get_contents and fopen

$options = [
    'zip' => [
        'password' => '1234'
    ]
];

$context = stream_context_create($options);
echo file_get_contents('zip://file.zip#file.txt', false, $context);

A better solution however to check if a file exists or not before reading it without worrying about encrypted archives is using ZipArchive

$zip = new ZipArchive;
if ($zip->open('file.zip') !== TRUE) {
    exit('failed');
}
if ($zip->locateName('file.txt') !== false) {
    echo 'File exists';
} else {
    echo 'File does not exist';
}

This will work (no need to know the password)

Note: To locate a folder using locateName method you need to pass it like folder/ with a forward slash at the end.

Rain
  • 3,416
  • 3
  • 24
  • 40