use itertools.chain:

import itertools, gzip

files = ['path2zipfile1', 'path2zipfile2']
it = (gzip.open(f, 'rt') for f in files)

for line in itertools.chain.from_iterable(it):
    print(line)

another version without itertools:

def gen(files):
    for f in files:
        fo = gzip.open(f, 'rt')
        while True:
            line = fo.readline()
            if not line:
                break
            yield line

files = ['path2zipfile1', 'path2zipfile2']
for line in gen(files):
    print(line)
Answer from georgexsh on Stack Overflow
Discussions

gzip - Python unzip multiple .gz files - Stack Overflow
Next you can go to the extracted location and read() individual files with open into a string. Concatenate those strings and save to foo.txt. ... import os, gzip, shutil dir_name = '/Users/username/Desktop/data' def gz_extract(directory): extension = ".gz" os.chdir(directory) for item in ... More on stackoverflow.com
🌐 stackoverflow.com
January 17, 2019
linux - How to unzip multiple gz files in python using multi threading? - Stack Overflow
I have multiple gz files with a total size of around 120GB. I want to unzip(gzip) those files to the same directory and remove the existing gz file. Currently we are doing it manually and it is tak... More on stackoverflow.com
🌐 stackoverflow.com
December 24, 2015
How do i gzip multiple files using python - Stack Overflow
Pavan here, I've been trying to gzip multiple files using python scripting. I've files named as like 1.txt, 2.txt --- 99.txt. I've to convert all the .txt to .gz files. I know to gzip one file at o... More on stackoverflow.com
🌐 stackoverflow.com
September 27, 2021
zip - how to read multiple .gz files in a particular directory in python without unzipping them - Stack Overflow
I have a folder /var/tmp in my ... i have multiple .gz files in the below mentioned format (name_yyyymmddhhmmss.gz). aakashdeep_20181120080005.gz aakashdeep_20181120080025.gz kalpana_20181119080005.gz aakashdeep_20181120080025.gz · Now i want to open all the gz files with format as name_20181120*.gz without unzipping them and read the content out of them. ... and the same is giving me the output as expected, but i want to open all the files like below output = gzip.open('/va... More on stackoverflow.com
🌐 stackoverflow.com
November 20, 2018
🌐
Python
docs.python.org › 3 › library › gzip.html
gzip — Support for gzip files
Note that additional file formats which can be decompressed by the gzip and gunzip programs, such as those produced by compress and pack, are not supported by this module. ... Open a gzip-compressed file in binary or text mode, returning a file object.
🌐
Python Module of the Week
pymotw.com › 2 › gzip
gzip – Read and write GNU zip files - Python Module of the Week
Now available for Python 3! Buy the book! ... The gzip module provides a file-like interface to GNU zip files, using zlib to compress and uncompress the data. The module-level function open() creates an instance of the file-like class GzipFile. The usual methods for writing and reading data ...
Top answer
1 of 2
11

You can do this very easily with multiprocessing Pools:

import gzip
import multiprocessing
import shutil

filenames = [
    'a.gz',
    'b.gz',
    'c.gz',
    ...
]

def uncompress(path):
    with gzip.open(path, 'rb') as src, open(path.rstrip('.gz'), 'wb') as dest:
        shutil.copyfileobj(src, dest)

with multiprocessing.Pool() as pool:
    for _ in pool.imap_unordered(uncompress, filenames, chunksize=1):
        pass

This code will spawn a few processes, and each process will extract one file at a time.

Here I've chosen chunksize=1, to avoid stalling processes if some files are bigger than average.

2 of 2
2

A large segment of of the wall clock time spent decompressing a file with gunzip or gzip -d will be from the I/O operations (reading and writing to disk). It might even be more than the time spent actually decompressing data. You can take advantage of this by having multiple gzip jobs going in the background. As some jobs are blocked on I/O, another job can actually run without having to wait in a queue.

You can speed up the decompressing of the entire file set by having multiple gunzip processes running in the background. Each serving a specific set of files.

You can whip up something easy in BASH. Split the file list into separate commands and use the & to start it as a background job. Then wait for each each job to finish.

I would recommend that you have between 2 to 2*N jobs going at once. Where N is the number of cores or logical processors on your computer. Experiment as appropriate to get the right number.

You can whip something up easy in BASH.

#!/bin/bash

argarray=( "{#argarray[@]}

#declare 4 empty array sets
set1=()
set2=()
set3=()
set4=()

# enumerate over each argument passed to the script
# and round robin add it to one of the above arrays

i=0
while [ len ]
do

    if [ len ]; then
        set1+=( "${argarray[$i]}" )
        ((i++))
    fi

    if [ $i -lt $len ]; then
        set2+=( "${argarray[$i]}" )
        ((i++))
    fi

    if [ $i -lt $len ]; then
        set3+=( "${argarray[$i]}" )
        ((i++))
    fi

    if [ $i -lt $len ]; then
        set4+=( "${argarray[$i]}" )
        ((i++))
    fi
done

# for each array, start a background job
gzip -d ${set1[@]} &
gzip -d ${set2[@]} &
gzip -d ${set3[@]} &
gzip -d ${set4[@]} &

# wait for all jobs to finish    
wait

In the above example, I picked 4 files per job and started two separate jobs. You can easily extend the script to have more jobs, more files per process, and to take the file names as command line parameters.

Find elsewhere
🌐
Stack Overflow
stackoverflow.com › questions › 53391125 › how-to-read-multiple-gz-files-in-a-particular-directory-in-python-without-unzip
zip - how to read multiple .gz files in a particular directory in python without unzipping them - Stack Overflow
November 20, 2018 - import glob import gzip ZIPFILES='/var/tmp/Aakashdeep/aakashdeep_20181120*.gz' filelist = glob.glob(ZIPFILES) for gzfile in filelist: # print("#Starting " + gzfile) #if you want to know which file is being processed with gzip.open( gzfile, 'r') as f: for line in f: print(line) ... Sign up to request clarification or add additional context in comments. ... Thanks Nigel. The code you mentioned above, worked for me. 2018-11-20T13:05:54.863Z+00:00 ... The pattern (for filename in glob.glob(...) then with) is one that I've used hundreds of times, but oddly never with gzip. It's the Python equivalent of most bash commands with a -r recusive option.
🌐
Stack Overflow
stackoverflow.com › questions › 36979257 › python-reading-and-processing-multiple-gzip-files-in-remote-server
Python: Reading and processing Multiple gzip files in remote server - Stack Overflow
May 2, 2016 - ..... ........ path_f='/home/user/may/'+filename #Read the Gzip file in local system after FTP is done with gzip.open(path_f, 'rb') as f: contents = f.read() if any(s in contents for s in strings): print "File " + str(path_f) + " is a hit." insort(ifile_list, filename) # Push the file into the list if there is a match.
Top answer
1 of 2
4

Since the split files do not need to be readable text files, I would read & write in chunks of bytes, not in lines. This should be faster than reading and writing line by line. We can go with a chunk size of 4,096 bytes because 4,096 or 8,192 is a typical block size on most file systems.

Although, I should note that when I tested your script on my system with a 9 GB text file which has 81 characters per line, it took about 73 seconds to split it into files of 10,000,000 lines each, or about 772 MB per file. The fact that it takes 12 minutes on your laptop likely confirms @Graipher's suspicion that your disk's write speed is the bottleneck here.

Using a refactored version of your script that reads & writes in byte chunks (provided at the end of this review), when I ran it on the same file with --size 772 (split into files of size 772 MB) it took about 35 seconds. Still a decent improvement, but I'd be curious to know how it runs on your machine.

To implement this, I personally like having a helper method like the following which tells us the chunk sizes to read/write:

from typing import Iterator

def chunk_sizes(total_bytes: int, chunk_size: int) -> Iterator[int]:
    for _ in range(total_bytes // chunk_size):
        yield chunk_size

    if remaining_bytes := total_bytes % chunk_size:
        yield remaining_bytes

For example, list(chunk_sizes(10000, 4096)) which returns [4096, 4096, 1808] would tell us that in order to read 10,000 bytes in 4,096 byte chunks, we should read 4,096 bytes, followed by another 4,096 bytes, followed by 1,808 bytes.

General review

  • There's no reason to have run() be a staticmethod, and it's weird how FileSplitter creates an instance of itself inside this method.

    If you change the top-level invocation to FileSplitter().run() then run() could be simplified to

    def run(self):
        self.split()
    

    But if you do that you might as well remove run() and make FileSplitter().split() the top-level call. My recommendation is to move the contents of run() under the __name__ == "__main__" guard and then remove run() entirely as it's no longer needed.

  • Always open files with a with block to ensure the file is closed properly.

  • I recommend using argparse because you get a lot of stuff for free (input type validations, help/usage message with -h, etc.).

  • I would move the command-line argument parsing logic out of FileSplitter, because it should ideally only have one responsibility: splitting files.

  • Specify the exact exception you want to handle with a try-except. Handling a bare except is considered a bad practice.

  • I wouldn't use sys.exit inside methods you might want to test, because it exits the Python interpreter.

  • In Python 3, f-strings are a much more legible way of doing string interpolation.

    Before

    new_file_name = "%s.%s" % (self.file_base_name, str(file_number))
    

    After

    new_file_name = f"{self.file_base_name}.{file_number}"
    

Refactored version

#!/usr/bin/env python3

import argparse
import gzip
import os
import itertools

from typing import BinaryIO, Iterator

CHUNK_SIZE = 4096  # bytes


def megabytes_to_bytes(megabytes: int) -> int:
    return megabytes * 1024 * 1024


def chunk_sizes(total_bytes: int, chunk_size: int) -> Iterator[int]:
    for _ in range(total_bytes // chunk_size):
        yield chunk_size

    if remaining_bytes := total_bytes % chunk_size:
        yield remaining_bytes


class FileSplitter:
    input_file_path: str
    output_directory: str
    output_file_base_name: str
    split_size: int  # size of each partition, in MB

    def __init__(
        self, input_file_path: str, output_directory: str, split_size: int
    ) -> None:
        self.input_file_path = input_file_path
        self.output_directory = output_directory
        name = os.path.basename(input_file_path)
        name_without_file_extension = os.path.splitext(name)[0]
        self.output_file_base_name = name_without_file_extension
        self.split_size = split_size

    def get_new_file(self, file_number: int) -> BinaryIO:
        new_file_name = f"{self.output_file_base_name}.{file_number}"
        new_file_path = os.path.join(self.output_directory, new_file_name)
        return open(new_file_path, "wb")

    def split(self) -> None:
        bytes_per_file = megabytes_to_bytes(self.split_size)
        os.makedirs(self.output_directory, exist_ok=True)

        with gzip.open(self.input_file_path, mode="rb") as in_file:
            for file_number in itertools.count(start=1, step=1):
                with self.get_new_file(file_number) as out_file:
                    for chunk_size in chunk_sizes(bytes_per_file, CHUNK_SIZE):
                        chunk = in_file.read(chunk_size)
                        # chunk == "" means we finished reading the input file
                        if not chunk:
                            return
                        out_file.write(chunk)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Read a gzip-compressed file "
        "and split its uncompressed contents into separate files."
    )
    parser.add_argument(
        "file_name", help="a gzip-compressed file, e.g. foo.gz"
    )
    parser.add_argument(
        "--size",
        type=int,
        default=500,
        help="desired size of each partition, in MB",
    )
    args = parser.parse_args()

    output_directory = os.path.join(os.getcwd(), "pychunks")
    splitter = FileSplitter(args.file_name, output_directory, args.size)
    splitter.split()
2 of 2
2

What would you change in this code?

I'd simplify the split function. Not counting lines and files myself and opening/closing the output files at different places without a with statement. Just always try to read another line and if that succeeds, put it and the next up to split_size - 1 lines into another output file.

    def split(self):
        print("Splitting %s into multiple files with %s lines" % (os.path.join(self.working_dir, self.file_base_name), str(self.split_size)))

        with gzip.open(self.in_file, 'rt') as fin:
            for file_number, line in enumerate(fin, start=1):
                with self.get_new_file(file_number) as fout:
                    fout.write(line)
                    fout.writelines(itertools.islice(fin, self.split_size - 1))

        print("Created %s files." % (str(file_number)))

While I do frequently use f when I only work on one file, I didn't like the very different names f and out_file. In such cases I use fin and fout (and I think that's rather common), so I changed that as well.

I'd probably also change the inner with to with open(...) as fout and have get_new_file return only the filename, not open the file itself. I'd rather keep the open in the with statement and have get_new_file be a function without such side effects. Or if your only reason for having get_new_file at all was that you had two places for using it, then now you don't need it anymore and could just build the filename inside the split function.

Still unfixed issues in your code:

  • While you fixed one line_number = 1 (from your original) to line_number = 0, you forgot the other one. This makes your chunks one line too short. Wouldn't happen if you only had one place :-)
  • Lacking import of sys, causing NameError: name 'sys' is not defined.
  • Lacking creation of folder pychunks. Without that, the user gets a rather confusing traceback and FileNotFoundError: [Errno 2] No such file or directory: '/home/runner/p/pychunks/main.py.gz.1'.
  • t = next(a_file) loses the first line, as that is never used. Might be what you want, but it's not documented and goes against common sense and the description that is there and gets shown when you run the script without arguments. Perhaps if you do want that, make it a command-line argument for skipping the first number of lines, with default 0.
🌐
Medium
medium.com › bright-ai › unzip-multiple-files-with-python-d3823ccfea25
Big Data: Unzip multiple .gz files with python - ProdAI - Medium
October 17, 2022 - Big Data: Unzip multiple .gz files with python Below is a code to extract multiple files in a folder and replace those with unzipped files. This works for .gz files on Mac Step 1: In a code editor …
🌐
GitHub
github.com › Smerity › gzipstream
GitHub - Smerity/gzipstream: gzipstream allows Python to process multi-part gzip files from a streaming source · GitHub
This is highly inefficient as (a) a gzipped WARC file is composed of multiple independent gzip files and (b) the WARC file is hunderds of megabytes in size. For minimal usage however... from gzipstream import GzipStreamFile f = open('huge_file.gz') # Any streaming file object that supports `read` gz = GzipStreamFile(f)
Starred by 17 users
Forked by 19 users
Languages   Python
🌐
Python.org
discuss.python.org › ideas
Multithreaded gzip reading and writing - Ideas - Discussions on Python.org
February 21, 2023 - It is possible to write a gzip file with multiple thread. The pigz implementation that Mark Adler made contains comments on how to do so efficiently. gzip reading is limited to single-thread (although the crc could be checked in a separate thread). Since CPython’s zlib module allows escaping the GIL when multiple threads are used this could be used to allow the use of multiple cores when compressing gzip files.
Top answer
1 of 2
2

Yes. Use z = zlib.decompressobj(31), and then use z to decompress until z.unused_data is not empty, or you have processed all of the input. If you get z.unused_data as not empty, then it contains the start of the next gzip stream. Create a new y = zlib.decompressobj object, and start decompression with the contents of z.unused_data, continuing with more data from the file.

This prints the uncompressed size of each concatenated gzip component:

#!/usr/bin/python
import sys
import zlib
z = zlib.decompressobj(31)
count = 0
while True:
    if z.unused_data == "":
        buf = sys.stdin.read(8192)
        if buf == "":
            break
    else:
        print count
        count = 0
        buf = z.unused_data
        z = zlib.decompressobj(31)
    got = z.decompress(buf)
    count += len(got)
print count
2 of 2
0

@MarkAdler Thank you very much for this answer. It actually helped me quite a bit !

Now I just want to add a tiny detail that can save a lot of your time. The current answer will not detect truncated files such as gzip/zcat would.

zcat file.gz 
gzip: file.gz: unexpected end of file

To correct this, check decompress.oef. If False, this means the gzip file is truncated. If you don't do this, you'll never see an error.

Here is the modified code:

#!/usr/bin/python
import sys
import zlib
z = zlib.decompressobj(31)
count = 0
while True:
    if z.unused_data == "":
        buf = sys.stdin.read(8192)
        if buf == "":
            # check truncated file
            if not z.eof:
                raise RuntimeError("unexpected end of file")
            break
    else:
        print count
        count = 0
        buf = z.unused_data
        z = zlib.decompressobj(31)
    got = z.decompress(buf)
    count += len(got)
print count
🌐
Reddit
reddit.com › r/learnpython › how to unzip a massive amount of files using windows python 2.7?
r/learnpython on Reddit: How to unzip a massive amount of files using Windows Python 2.7?
June 8, 2013 -

I have millions of zip files (.gz extension) that I need EXTRACTED. Each of the zip files has one .txt file in them and I need that .txt file to use in data analysis. Unzipping them manually would take weeks, does anyone have an easily modifiable script out there that they are willing to share? I have very little experience with Python. Thank you in advance.

Edit: should have said "Extract" instead of "unzip."

🌐
Reddit
reddit.com › r/learnpython › converting multiple gzip files in a directory to csv
r/learnpython on Reddit: Converting multiple gzip files in a directory to csv
November 19, 2019 -

Hi All,

I am a beginner with Python trying to solve a few problems with my daily workflow in a non-tech job. I recently wrote a script that I feel could use some improvement, but I am not knowledgeable enough to know how to make it more efficient.

Background: I sometimes need to download multiple large compressed .txt.gz files from our server and convert them into .csv before I offload them to other business units for review. Instead of extracting manually and pasting into Excel, I wanted to write a script to take care of it. Here is what I came up with after some YouTube and StackOverflow searching (the delimiter in these txt files is a '|' instead of the standard ',':

import csv
import shutil
import gzip
import os

src_dir = 'C:\\Users\\user\\Downloads\\'
dest_dir = 'C:\\Users\\user\\Desktop\\Python\\extractedgzs\\'

f_names = []

# get file names
for files in os.listdir(src_dir):
    if files.endswith('.txt.gz'):
        f_names.append(files)

#file found confirmation
print('found these files:')
print(f_names)


# unzip gz file to dest dir
for name in f_names:
    with gzip.open(src_dir+name, 'rb') as f_in:
     with open(dest_dir+name[0:-6], 'wb') as f_out:
        shutil.copyfileobj(f_in, f_out)

    # extracted file to .csv
    with open(dest_dir+name[0:-6], 'r') as in_file:
        stripped = (line.strip() for line in in_file)
        lines = (line.split("|") for line in stripped if line)
        with open(dest_dir+name[0:-6]+'csv', 'w', newline='') as out_file:
            writer = csv.writer(out_file)
            writer.writerows(lines)

I understand that this code is rather rigid but that it is fine since I am the only person who will ever use it and it only needs to serve this one purpose.

The main issue I have with this is that I generate an text file that is used up by the splitter/csv writer for writing the csv. I was wondering if there is any way to eliminate this step, or at the very least automate deleting that file once all the csv's are written.

I appreciate any tips you can offer!