Q: How to search a string from the nested zip file using python 3.10.5? I am trying to search string from the zip file which has a structure like the below:


├───some_zip_file.zip
│   ├──some_directory
│   │   ├──zip1.zip
│   │   ├──zip2.zip
│   │   ├──zip3.zip 
│   │   ├   ├──File1 \\ search in this file


I used the below code but it is not searching inside the folder, I went through many online forums but unable to get the resolution of this. Can someone please help?
import io
import zipfile

search_string = 'ERROR '
exclude_file = 'Test'
with zipfile.ZipFile("C:\\Python testing\\Logs-node1.zip") as zf:
    for filename in zf.infolist():
        if filename.find(exclude_file) == -1:
            with io.TextIOWrapper(zf.open(filename), encoding="utf-8", errors='ignore') as f:
                for line_no, line in enumerate(f, 1):
                    if search_string.lower() in line.lower():
                        # print(filename + ' : ' + str(line_no) + ' : ' + line)
                        file1 = open("C:\\Python testing\\my file.txt", "w")
                        file1.write(filename + ' : ' + str(line_no) + ' : ' + line)
                        file1.close()
            f.close()


A: You should be able to follow what's happening in the code.
import zipfile
import os

search_string = 'ERROR '
exclude_file = 'Test.txt' # include filename with extension
outputfile = 'C:\\Python testing\\my file.txt'
rootzipfile = 'C:\\Python testing\\Logs-node1.zip'
PATH_SEPARATOR = os.sep
                
def find_string_in_nested_zip(filepath, zf=None, extra_path_info=''):
    if not(is_zip_or_nested_zip(filepath, zf)) and not(is_dir(filepath)) and \
        not(filepath.endswith(exclude_file)) and file_contains_text(filepath, zf):
        write_text_with_line_no(filepath, zf, extra_path_info)
    
    elif zipfile.is_zipfile(filepath):
        make_recursive_call_for_zip(filepath)
    
    elif is_nested_zip(filepath, zf):
        with zf.open(filepath, 'r') as f:
            make_recursive_call_for_zip(f, create_extra_path_info(filepath))

# must open the nested zip file before checking otherwise doesn't give the correct result             
def is_nested_zip(filename, zf):
    with zf.open(filename, 'r') as f:
        if zipfile.is_zipfile(f):
            return True
        return False
    
def is_zip_or_nested_zip(filename, zf):
    return zipfile.is_zipfile(filename) or is_nested_zip(filename, zf)

# os.path.isdir() does not work for dirs inside zips so we write our own
def is_dir(filepath):
    return filepath.endswith(PATH_SEPARATOR)
                
def file_contains_text(filename, zf):
    with zf.open(filename, 'r') as f:
        if search_string.encode() in f.read():
            return True
        return False

def write_text_with_line_no(filename, zf, extra_path_info):
    with zf.open(filename, 'r') as f, open(outputfile, 'a+') as op:
        for (i, line) in enumerate(f):
            if search_string.encode() in line:
                op.write(f'{extra_path_info + PATH_SEPARATOR + filename}: line{str(i+1)} : {line.decode()}')

# extra_path_info makes sure when accessing nested zips the path up to the nested zip is preserved
def create_extra_path_info(filepath):
    return PATH_SEPARATOR.join(filepath.split(PATH_SEPARATOR)[:-1])

def make_recursive_call_for_zip(filepath, extra_path_info=''):
    with zipfile.ZipFile(filepath, 'r') as zf:
        for filename in zf.namelist():
            find_string_in_nested_zip(filename, zf, extra_path_info)

find_string_in_nested_zip(rootzipfile)

