I'm using ZipArchive and I want to extract ZIP file.
I can see a comment in the docs for ZipArchive::extractTo:
Note, in Linux (possibly other *nix platforms too) there is no way to extract hidden files ( aka filename starting with a '.') from a Zip archive.
And very important note: I need it to work on both *nix and Windows platforms.
So how to properly extract ZIP file into dir, including hidden files?
Thanks in advance,
JK.
You could use the system() or exec() functions if the server allows them :
system("unzip archive.zip");
There are although shell_exec() and passthru().
Hope it helps.
EDIT
Since OP looking for pure-php solution here is a user implemented function for php manual (Zip Functions) :
<?php
/**
* Unzip the source_file in the destination dir
*
* #param string The path to the ZIP-file.
* #param string The path where the zipfile should be unpacked, if false the directory of the zip-file is used
* #param boolean Indicates if the files will be unpacked in a directory with the name of the zip-file (true) or not (false) (only if the destination directory is set to false!)
* #param boolean Overwrite existing files (true) or not (false)
*
* #return boolean Succesful or not
*/
function unzip($src_file, $dest_dir=false, $create_zip_name_dir=true, $overwrite=true)
{
if ($zip = zip_open($src_file))
{
if ($zip)
{
$splitter = ($create_zip_name_dir === true) ? "." : "/";
if ($dest_dir === false) $dest_dir = substr($src_file, 0, strrpos($src_file, $splitter))."/";
// Create the directories to the destination dir if they don't already exist
create_dirs($dest_dir);
// For every file in the zip-packet
while ($zip_entry = zip_read($zip))
{
// Now we're going to create the directories in the destination directories
// If the file is not in the root dir
$pos_last_slash = strrpos(zip_entry_name($zip_entry), "/");
if ($pos_last_slash !== false)
{
// Create the directory where the zip-entry should be saved (with a "/" at the end)
create_dirs($dest_dir.substr(zip_entry_name($zip_entry), 0, $pos_last_slash+1));
}
// Open the entry
if (zip_entry_open($zip,$zip_entry,"r"))
{
// The name of the file to save on the disk
$file_name = $dest_dir.zip_entry_name($zip_entry);
// Check if the files should be overwritten or not
if ($overwrite === true || $overwrite === false && !is_file($file_name))
{
// Get the content of the zip entry
$fstream = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));
file_put_contents($file_name, $fstream );
// Set the rights
chmod($file_name, 0777);
echo "save: ".$file_name."<br />";
}
// Close the entry
zip_entry_close($zip_entry);
}
}
// Close the zip-file
zip_close($zip);
}
}
else
{
return false;
}
return true;
}
Related
I'm trying to upload multiple files to a SFTP site from a local directory.
I can get it to work for a single file but I would like to be able to upload multiple files which have variables names too.
$localFile_xml = "C:\xml\Race_" . $value;
chdir($localFile_xml);
//This successfully lists the files
foreach (glob("*.xml") as $filename) {
echo "$filename size " . filesize($filename) . "\n";
}
$remote_XMLfiles = "/FTP/XML/Race_" . $value;
$xmlstream = fopen("ssh2.sftp://$sftp" . $remote_XMLfiles, 'w');
foreach (glob("*.xml") as $filename) {
$xmlfile = file_get_contents($localFile_xml);
fwrite($xmlstream, $xmlfile);
fclose($xmlstream);
}
I believe its there but I cannot get the last bit correct.
Thank you so much in advance
Assuming the remote SSH connection is valid, and that the method you used in your question works for single files, I believe your order of operations needs to be corrected.
As mentioned in my comments, your code appears to be attempting to use file_get_contents on a local directory, which is not permitted. It also appears you attempted the same on the $xmlstream, which must be performed per file, rather than directory. Assuming 'C:\\xml\\Race_' . $value; is a directory like C:\\xml\\Race_1 and not a file.
Some minor issues for validation of resources and Windows specific issues that need to be addressed:
Windows Directory Separators should be written as \\ (even when
using single quotes), since \ is an escape sequence
it causes \x \t \n \r \' \" \\ to be treated as special characters.
When using fopen($path, $mode) it is recommended to specify the b flag as the last character of the mode, to ensure the file is
binary-safe (verbatim) and to avoid ambiguity between operating systems. Alternatively on Windows specify the t mode to transparently translate \n to \r\n (only desirable for plain-text files).
$mode = 'rb' (binary-safe read)
$mode = 'rt' (text-mode translation read)
When working with networking streams, it is recommended to test that the stream has successfully written all of its content. I provided the fwrite_stream function below from the PHP manual.
Example
try {
//--- example purposes only ---
//use your own ssh2_connnect, ssh2_auth_password, ssh2_sftp
if (!$ssh2 = ssh2_connect('192.168.56.101', 22)) {
throw new \RuntimeException('Unable to connect to remote host');
}
if (!ssh2_auth_password($ssh2, 'root', '')) {
throw new \RuntimeException('Unable to Authenticate');
}
if (!$sftp = ssh2_sftp($ssh2)) {
throw new \RuntimeException('Unable to initialize SFTP');
}
$value = '1';
//--- end example purposes only ---
$localFile_xml = 'C:\\xml\\Race_' . $value;
if (!$localFile_xml || !is_dir($localFile_xml)) {
throw new \RuntimeException('Unable to retrieve local directory');
}
//retrieve list of XML files
$iterator = new \GlobIterator($localFile_xml . '/*.xml',
\FilesystemIterator::KEY_AS_PATHNAME |
\FilesystemIterator::CURRENT_AS_FILEINFO |
\FilesystemIterator::SKIP_DOTS
);
if (!$iterator->count()) {
throw new \RuntimeException('Unable to retrieve local files');
}
$success = [];
$remote_XMLfiles = '/FTP/XML/Race_' . $value;
$remote_XMLpath = "ssh2.sftp://$sftp" . $remote_XMLfiles;
//ensure the remote directory exists
if (!#mkdir($remote_XMLpath, 0777, true) && !is_dir($remote_XMLpath)) {
throw new \RuntimeException(sprintf('Unable to create remote directory "%s"', $remote_XMLpath));
}
/**
* #var string $filepath
* #var \SplFileInfo $fileinfo
*/
foreach ($iterator as $filepath => $fileinfo) {
$filesize = $fileinfo->getSize();
printf("%s size %d\n", $filepath, $filesize);
try {
//open local file resource for binary-safe reading
$xmlObj = $fileinfo->openFile('rb');
//retrieve entire file contents
if (!$xmlData = $xmlObj->fread($filesize)) {
//do not permit empty files
printf("No data found for \"%s\"\n", $filepath);
continue;
}
} finally {
//shortcut to close the opened local file resource on success or fail
$xmlObj = null;
unset($xmlObj);
}
try {
$remote_filepath = $remote_XMLpath . '/' . $fileinfo->getBasename();
//open a remote file resource for binary-safe writing
//using current filename, overwriting the file if it already exists
if (!$xmlstream = fopen($remote_filepath, 'wb')) {
throw new \RuntimeException(sprintf('Unable to create remote file "%s"', $remote_filepath));
}
//write the local file data to the remote file stream
if (false !== ($bytes = fwrite_stream($xmlstream, $xmlData))) {
$success[] = [
'filepath' => $filepath,
'remote_filepath' => $remote_filepath,
'bytes' => $bytes,
];
}
} finally {
//shortcut to ensure the xmlstream is closed on success or failure
if (isset($xmlstream) && is_resource($xmlstream)) {
fclose($xmlstream);
}
}
}
//testing purposes only to show the resulting uploads
if (!empty($success)) {
var_export($success);
}
} finally {
//shortcut to disconnect the ssh2 session on success or failure
$sftp = null;
unset($sftp);
if (isset($ssh2) && is_resource($ssh2)) {
ssh2_disconnect($ssh2);
}
}
/*
* Taken from PHP Manual
* Writing to a network stream may end before the whole string is written.
* Return value of fwrite() may be checked
*/
function fwrite_stream($fp, $string)
{
for ($written = 0, $writtenMax = strlen($string); $written < $writtenMax; $written += $fwrite) {
$fwrite = fwrite($fp, substr($string, $written));
if (false === $fwrite) {
return $written;
}
}
return $written;
}
NOTE
All file operations will be created using the ssh2_auth_password
user as the owner/group. You must ensure the specified user has read
and write access to the desired directories.
Use the appropriate file masks to ensure desired file/directory permissions
0777 (default) allows everyone to read, write, execute!
0750 is typically desired for directories
0640 is typically desired for individual files
use chmod($path, 0750) to change permissions on the remote file(s)
use chown($path, 'user') to change the owner on the remote file(s)
use chgrp($path, 'group') to change the group on the remote file(s)
Result
C:\xml\Race_1\file1.xml size 9
C:\xml\Race_1\file2.xml size 11
array (
0 =>
array (
'filepath' => 'C:\\xml\\Race_1\\file1.xml',
'remote_filepath' => 'ssh2.sftp://Resource id #5/FTP/XML/Race_1/file1.xml',
'bytes' => 9,
),
1 =>
array (
'filepath' => 'C:\\xml\\Race_1\\file2.xml',
'remote_filepath' => 'ssh2.sftp://Resource id #5/FTP/XML/Race_1/file2.xml',
'bytes' => 11,
),
)
I connect via ftp_connect and ftp_login to a FTP server. Once connected, I go to a directory with ftp_chdir. In the directory, I have to delete with ftp_delete all files that have the word "ub" in their filenames. So I have to read somehow every filename and delete only those files who have "ub" in their filenames. I have no idea how to do this. Please help. Thanks.
If you use an interactive ftp command-line tool, you can issue the command
mdel *ub*
but the low-level protocol doesn't support wildcard operations; this is something that has to be implemented in the client by fetching all the names, comparing against the pattern, and deleting one-by-one, as you said. You might want to consider scripting this using command-line ftp, rather than using php?
#Pekka's comment has one possible solution. Another is using glob.
$files = glob('*ub*');
foreach (glob("*ub*") as $file) {
ftp_delete('YOUR_CONNECTION', $file);
}
regards
Since there is no real answer to this question, I will answer with my functions that allows to delete multiple files over ftp:
/**
* Delete multiple files on FTP server. Allowed wildcards are * and ?.
* #param resource $ftp_connection
* #param string $delete_pattern
* #param bool $case_sensitive Case sensitivity is by default
* #return bool|int Number of deleted files, FALSE on failure
*/
function ftp_mdelete($ftp_connection, $delete_pattern = "", $case_sensitive = TRUE){
if(!is_resource($ftp_connection) || strtolower(get_resource_type($ftp_connection)) !== "ftp buffer"){
trigger_error("First parameter for ftp_mdelete should be a valid FTP connection", E_USER_WARNING);
return FALSE;
}elseif(!is_string($delete_pattern) || !strlen($delete_pattern)){
trigger_error("Second parameter for ftp_mdelete should be a non-empty string", E_USER_WARNING);
return FALSE;
}
$raw_list = ftp_rawlist($ftp_connection, '.');
if(!is_array($raw_list)){
return FALSE;
}
$matched_count = 0;
$deleted_count = 0;
if($raw_list){
$delete_pattern = preg_quote($delete_pattern);
$delete_pattern = '/^'.str_replace(array('\*', '\?'), array('.*', '.'), $delete_pattern).'/S'.($case_sensitive?'':'i');
foreach($raw_list as $entry){
if($entry{0} === '-'){
$entry = preg_split("/[\s]+/S", $entry, 9);
$entry = $entry[8];
if(preg_match($delete_pattern, $entry)){
++$matched_count;
if(ftp_delete($ftp_connection, $entry)){
++$deleted_count;
}
}
}
}
unset($raw_list, $entry);
}
if($matched_count != $deleted_count && $deleted_count){
trigger_error("Only {$deleted_count} out of {$matched_count} files deleted.", E_USER_NOTICE);
}elseif($matched_count && !$deleted_count){
trigger_error("No files were deleted ({$matched_count} files matched given pattern).", E_USER_WARNING);
return FALSE;
}
return $deleted_count;
}
Usage example:
$ftp = ftp_connect('127.0.0.1');
ftp_login($ftp, 'user', 'pass');
ftp_chdir($ftp, 'dir');
$deleted = ftp_mdelete($ftp, '*ub*');
ftp_close($ftp);
echo "Number of deleted files: ".intval($deleted);
I would like to gzip compress a file on my server using PHP. Does anyone have an example that would input a file and output a compressed file?
This code does the trick
// Name of the file we're compressing
$file = "test.txt";
// Name of the gz file we're creating
$gzfile = "test.gz";
// Open the gz file (w9 is the highest compression)
$fp = gzopen ($gzfile, 'w9');
// Compress the file
gzwrite ($fp, file_get_contents($file));
// Close the gz file and we're done
gzclose($fp);
The other answers here load the entire file into memory during compression, which will cause 'out of memory' errors on large files. The function below should be more reliable on large files as it reads and writes files in 512kb chunks.
/**
* GZIPs a file on disk (appending .gz to the name)
*
* From http://stackoverflow.com/questions/6073397/how-do-you-create-a-gz-file-using-php
* Based on function by Kioob at:
* http://www.php.net/manual/en/function.gzwrite.php#34955
*
* #param string $source Path to file that should be compressed
* #param integer $level GZIP compression level (default: 9)
* #return string New filename (with .gz appended) if success, or false if operation fails
*/
function gzCompressFile($source, $level = 9){
$dest = $source . '.gz';
$mode = 'wb' . $level;
$error = false;
if ($fp_out = gzopen($dest, $mode)) {
if ($fp_in = fopen($source,'rb')) {
while (!feof($fp_in))
gzwrite($fp_out, fread($fp_in, 1024 * 512));
fclose($fp_in);
} else {
$error = true;
}
gzclose($fp_out);
} else {
$error = true;
}
if ($error)
return false;
else
return $dest;
}
UPDATE: Gerben has posted an improved version of this function that is cleaner and uses exceptions instead of returning false on an error. See https://stackoverflow.com/a/56140427/195835
Also, you could use php's wrappers, the compression ones. With a minimal change in the code you would be able to switch between gzip, bzip2 or zip.
$input = "test.txt";
$output = $input.".gz";
file_put_contents("compress.zlib://$output", file_get_contents($input));
change compress.zlib:// to compress.zip:// for zip compression (see comment to this answer about zip compression), or to compress.bzip2:// to bzip2 compression.
Simple one liner with gzencode():
gzencode(file_get_contents($file_name));
If you are looking to just unzip a file, this works and doesn't cause issues with memory:
$bytes = file_put_contents($destination, gzopen($gzip_path, r));
It's probably obvious to many, but if any of the program execution functions is enabled on your system (exec, system, shell_exec), you can use them to simply gzip the file.
exec("gzip ".$filename);
N.B.: Be sure to properly sanitize the $filename variable before using it, especially if it comes from user input (but not only). It may be used to run arbitrary commands, for example by containing something like my-file.txt && anothercommand (or my-file.txt; anothercommand).
Here's an improved version. I got rid of all the nested if/else statements, resulting in lower cyclomatic complexity, there's better error handling through exceptions instead of keeping track of a boolean error state, some type hinting and I'm bailing out if the file has a gz extension already. It got a little longer in terms of lines of code, but it's much more readable.
/**
* Compress a file using gzip
*
* Rewritten from Simon East's version here:
* https://stackoverflow.com/a/22754032/3499843
*
* #param string $inFilename Input filename
* #param int $level Compression level (default: 9)
*
* #throws Exception if the input or output file can not be opened
*
* #return string Output filename
*/
function gzcompressfile(string $inFilename, int $level = 9): string
{
// Is the file gzipped already?
$extension = pathinfo($inFilename, PATHINFO_EXTENSION);
if ($extension == "gz") {
return $inFilename;
}
// Open input file
$inFile = fopen($inFilename, "rb");
if ($inFile === false) {
throw new \Exception("Unable to open input file: $inFilename");
}
// Open output file
$gzFilename = $inFilename.".gz";
$mode = "wb".$level;
$gzFile = gzopen($gzFilename, $mode);
if ($gzFile === false) {
fclose($inFile);
throw new \Exception("Unable to open output file: $gzFilename");
}
// Stream copy
$length = 512 * 1024; // 512 kB
while (!feof($inFile)) {
gzwrite($gzFile, fread($inFile, $length));
}
// Close files
fclose($inFile);
gzclose($gzFile);
// Return the new filename
return $gzFilename;
}
Compress folder for anyone needs
function gzCompressFile($source, $level = 9)
{
$tarFile = $source . '.tar';
if (is_dir($source)) {
$tar = new PharData($tarFile);
$files = scandir($source);
foreach ($files as $file) {
if (is_file($source . '/' . $file)) {
$tar->addFile($source . '/' . $file, $file);
}
}
}
$dest = $tarFile . '.gz';
$mode = 'wb' . $level;
$error = false;
if ($fp_out = gzopen($dest, $mode)) {
if ($fp_in = fopen($tarFile, 'rb')) {
while (!feof($fp_in))
gzwrite($fp_out, fread($fp_in, 1024 * 512));
fclose($fp_in);
} else {
$error = true;
}
gzclose($fp_out);
unlink($tarFile);
} else {
$error = true;
}
if ($error)
return false;
else
return $dest;
}
copy('file.txt', 'compress.zlib://' . 'file.txt.gz');
See documentation
I want to create a directory if it does not exist already.
Is using the is_dir function enough for that purpose?
if ( !is_dir( $dir ) ) {
mkdir( $dir );
}
Or should I combine is_dir with file_exists?
if ( !file_exists( $dir ) && !is_dir( $dir ) ) {
mkdir( $dir );
}
Both would return true on Unix systems - in Unix everything is a file, including directories. But to test if that name is taken, you should check both. There might be a regular file named 'foo', which would prevent you from creating a directory name 'foo'.
$filename = "/folder/" . $dirname . "/";
if (file_exists($filename)) {
echo "The directory $dirname exists.";
} else {
mkdir("folder/" . $dirname, 0755);
echo "The directory $dirname was successfully created.";
exit;
}
I think realpath() may be the best way to validate if a path exist
http://www.php.net/realpath
Here is an example function:
<?php
/**
* Checks if a folder exist and return canonicalized absolute pathname (long version)
* #param string $folder the path being checked.
* #return mixed returns the canonicalized absolute pathname on success otherwise FALSE is returned
*/
function folder_exist($folder)
{
// Get canonicalized absolute pathname
$path = realpath($folder);
// If it exist, check if it's a directory
if($path !== false AND is_dir($path))
{
// Return canonicalized absolute pathname
return $path;
}
// Path/folder does not exist
return false;
}
Short version of the same function
<?php
/**
* Checks if a folder exist and return canonicalized absolute pathname (sort version)
* #param string $folder the path being checked.
* #return mixed returns the canonicalized absolute pathname on success otherwise FALSE is returned
*/
function folder_exist($folder)
{
// Get canonicalized absolute pathname
$path = realpath($folder);
// If it exist, check if it's a directory
return ($path !== false AND is_dir($path)) ? $path : false;
}
Output examples
<?php
/** CASE 1 **/
$input = '/some/path/which/does/not/exist';
var_dump($input); // string(31) "/some/path/which/does/not/exist"
$output = folder_exist($input);
var_dump($output); // bool(false)
/** CASE 2 **/
$input = '/home';
var_dump($input);
$output = folder_exist($input); // string(5) "/home"
var_dump($output); // string(5) "/home"
/** CASE 3 **/
$input = '/home/..';
var_dump($input); // string(8) "/home/.."
$output = folder_exist($input);
var_dump($output); // string(1) "/"
Usage
<?php
$folder = '/foo/bar';
if(FALSE !== ($path = folder_exist($folder)))
{
die('Folder ' . $path . ' already exist');
}
mkdir($folder);
// Continue do stuff
Second variant in question post is not ok, because, if you already have file with the same name, but it is not a directory, !file_exists($dir) will return false, folder will not be created, so error "failed to open stream: No such file or directory" will be occured. In Windows there is a difference between 'file' and 'folder' types, so need to use file_exists() and is_dir() at the same time, for ex.:
if (file_exists('file')) {
if (!is_dir('file')) { //if file is already present, but it's not a dir
//do something with file - delete, rename, etc.
unlink('file'); //for example
mkdir('file', NEEDED_ACCESS_LEVEL);
}
} else { //no file exists with this name
mkdir('file', NEEDED_ACCESS_LEVEL);
}
I had the same doubt, but see the PHP docu:
https://www.php.net/manual/en/function.file-exists.php
https://www.php.net/manual/en/function.is-dir.php
You will see that is_dir() has both properties.
Return Values is_dir
Returns TRUE if the filename exists and is a directory, FALSE otherwise.
$year = date("Y");
$month = date("m");
$filename = "../".$year;
$filename2 = "../".$year."/".$month;
if(file_exists($filename)){
if(file_exists($filename2)==false){
mkdir($filename2,0777);
}
}else{
mkdir($filename,0777);
}
$save_folder = "some/path/" . date('dmy');
if (!file_exists($save_folder)) {
mkdir($save_folder, 0777);
}
This is an old, but still topical question. Just test with the is_dir() or file_exists() function for the presence of the . or .. file in the directory under test. Each directory must contain these files:
is_dir("path_to_directory/.");
Well instead of checking both, you could do if(stream_resolve_include_path($folder)!==false). It is slower but kills two birds in one shot.
Another option is to simply ignore the E_WARNING, not by using #mkdir(...); (because that would simply waive all possible warnings, not just the directory already exists one), but by registering a specific error handler before doing it:
namespace com\stackoverflow;
set_error_handler(function($errno, $errm) {
if (strpos($errm,"exists") === false) throw new \Exception($errm); //or better: create your own FolderCreationException class
});
mkdir($folder);
/* possibly more mkdir instructions, which is when this becomes useful */
restore_error_handler();
This is how I do
if(is_dir("./folder/test"))
{
echo "Exist";
}else{
echo "Not exist";
}
A way to check if a path is directory can be following:
function isDirectory($path) {
$all = #scandir($path);
return $all !== false;
}
NOTE: It will return false for non-existant path too, but works perfectly for UNIX/Windows
i think this is fast solution for dir check.
$path = realpath($Newfolder);
if (!empty($path)){
echo "1";
}else{
echo "0";
}
file_get_contents("zip:///a/b/c.zip") is returning NULL. How can I read unzipped contents of a zip file in PHP 5+?
use ZipArchive class
$zip = new ZipArchive;
$zip->open('test.zip');
echo $zip->getFromName('filename.txt');
$zip->close();
Use zip_open and zip_read functions to do it.
Documentation to it you can find at http://php.net/manual/en/function.zip-read.php
<?php
/**
* This method unzips a directory within a zip-archive
*
* #author Florian 'x!sign.dll' Wolf
* #license LGPL v2 or later
* #link http://www.xsigndll.de
* #link http://www.clansuite.com
*/
function extractZip( $zipFile = '', $dirFromZip = '' )
{
define(DIRECTORY_SEPARATOR, '/');
$zipDir = getcwd() . DIRECTORY_SEPARATOR;
$zip = zip_open($zipDir.$zipFile);
if ($zip)
{
while ($zip_entry = zip_read($zip))
{
$completePath = $zipDir . dirname(zip_entry_name($zip_entry));
$completeName = $zipDir . zip_entry_name($zip_entry);
// Walk through path to create non existing directories
// This won't apply to empty directories ! They are created further below
if(!file_exists($completePath) && preg_match( '#^' . $dirFromZip .'.*#', dirname(zip_entry_name($zip_entry)) ) )
{
$tmp = '';
foreach(explode('/',$completePath) AS $k)
{
$tmp .= $k.'/';
if(!file_exists($tmp) )
{
#mkdir($tmp, 0777);
}
}
}
if (zip_entry_open($zip, $zip_entry, "r"))
{
if( preg_match( '#^' . $dirFromZip .'.*#', dirname(zip_entry_name($zip_entry)) ) )
{
if ($fd = #fopen($completeName, 'w+'))
{
fwrite($fd, zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)));
fclose($fd);
}
else
{
// We think this was an empty directory
mkdir($completeName, 0777);
}
zip_entry_close($zip_entry);
}
}
}
zip_close($zip);
}
return true;
}
// The call to exctract a path within the zip file
extractZip( 'clansuite.zip', 'core/filters' );
?>
look at the build in zip functions:
http://php.net/manual/en/book.zip.php
The zip:// protocol is provided by the ZIP extension of PHP. Check in your phpinfo() output whether the extension has been installed or not.
I am responding to the first part of the question i.e. using the file_get_contents method
'file_get_contents("zip:///a/b/c.zip")' Usually that method is used to read one particular file nested inside the zip file. In order to extract all the contents; others have given nice answers.
I am using PHP 7.2.34 on windows and later on in Linux. I kept a zip file at d:\data and this syntax works in windows. It does echo the contents of example1.py which is inside a "folder" in the ZIP file.
Possibly it is also to do with where/how the zip file was created. When I had created the zip file from within PHP, the internal delimiters were backslashes but when Windows created the zip file (by using the "Send to compressed folder" feature in Windows explorer) then Windows was using the Linux convention inside the zip file!
More testing is needed here to know which delimiter for the internal paths, is being used in the zip file
<?php
$str = file_get_contents('zip://d:/data/demo.zip#examples\\example1.py');
echo $str;
?>