28

Once I have all the files I require in a particular folder, I would like my python script to zip the folder contents.

Is this possible?

And how could I go about doing it?

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
Amara
  • 13,839
  • 9
  • 32
  • 28

4 Answers4

42

On python 2.7 you might use: shutil.make_archive(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]]).

base_name archive name minus extension

format format of the archive

root_dir directory to compress.

For example

 shutil.make_archive(target_file, format="bztar", root_dir=compress_me)    
jb.
  • 23,300
  • 18
  • 98
  • 136
32

Adapted version of the script is:

#!/usr/bin/env python
from __future__ import with_statement
from contextlib import closing
from zipfile import ZipFile, ZIP_DEFLATED
import os

def zipdir(basedir, archivename):
    assert os.path.isdir(basedir)
    with closing(ZipFile(archivename, "w", ZIP_DEFLATED)) as z:
        for root, dirs, files in os.walk(basedir):
            #NOTE: ignore empty directories
            for fn in files:
                absfn = os.path.join(root, fn)
                zfn = absfn[len(basedir)+len(os.sep):] #XXX: relative path
                z.write(absfn, zfn)

if __name__ == '__main__':
    import sys
    basedir = sys.argv[1]
    archivename = sys.argv[2]
    zipdir(basedir, archivename)

Example:

C:\zipdir> python -mzipdir c:\tmp\test test.zip

It creates 'C:\zipdir\test.zip' archive with the contents of the 'c:\tmp\test' directory.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • `zfn = absfn[len(basedir)+len(os.sep):]` why not `absbasedir=os.path.abspath(basedir); os.path.relpath(absfn,absbasedir)` ? Also the length of basedir and os.sep can be seen as constant so it should be outside the two for loops. – n611x007 Aug 16 '13 at 13:11
  • never heard of [contextlib.closing](http://docs.python.org/2/library/contextlib.html#contextlib.closing) before, but it makes the *with* statement backward compatible with **2.5**. Previously I've ran into error with ... the "with [statement] from future" ;), because some stdlib functions didn't support *with* in *2.5* (even if the statement itself worked with the import), while supporting it in *2.7*. I guess this could be avoided with `closing`. – n611x007 Aug 16 '13 at 13:17
  • I used this solution but had to modify the following line: zfn = absfn[len(basedir)+len(os.sep):] and substract one to the position as: zfn = absfn[len(basedir)+len(os.sep)-1:] otherwise the name of the file has a missing letter – Juan Osorio Sep 29 '16 at 22:01
  • @jfosoriot 1- it is Python 2.5 code. Try `shutil.make_archive` instead. 2- normalize whether `basedir` ends with a path separator (slash):`if len(basedir) > 1: basedir = basedir.rstrip(os.sep)` – jfs Sep 29 '16 at 22:46
5

Here is a recursive version

def zipfolder(path, relname, archive):
    paths = os.listdir(path)
    for p in paths:
        p1 = os.path.join(path, p) 
        p2 = os.path.join(relname, p)
        if os.path.isdir(p1): 
            zipfolder(p1, p2, archive)
        else:
            archive.write(p1, p2) 

def create_zip(path, relname, archname):
    archive = zipfile.ZipFile(archname, "w", zipfile.ZIP_DEFLATED)
    if os.path.isdir(path):
        zipfolder(path, relname, archive)
    else:
        archive.write(path, relname)
    archive.close()
Kozyarchuk
  • 21,049
  • 14
  • 40
  • 46
0

Both jfs's solution and Kozyarchuk's solution could work for the OP's use case, however:

  • jfs's solution zips all of the files in a source folder and stores them in the zip at the root level (not preserving the original source folder within the structure of the zip).
  • Kozyarchuk's solution inadvertently puts the newly-created zip file into itself since it is a recursive solution (e.g. creating new zip file "myzip.zip" with this code will result in the archive "myzip.zip" itself containing an empty file "myzip.zip")

Thus, here is a solution that will simply add a source folder (and any subfolders to any depth) to a zip archive. This is motivated by the fact that you cannot pass a folder name to the built-in method ZipFile.write() -- the function below, add_folder_to_zip(), offers a simple method to add a folder and all of its contents to a zip archive. Below code works for Python2 and Python3.

import zipfile
import os

def add_folder_to_zip(src_folder_name, dst_zip_archive):
    """ Adds a folder and its contents to a zip archive

        Args:
            src_folder_name (str): Source folder name to add to the archive
            dst_zip_archive (ZipFile):  Destination zip archive

        Returns:
            None
    """
    for walk_item in os.walk(src_folder_name):
        for file_item in walk_item[2]:
            # walk_item[2] is a list of files in the folder entry
            # walk_item[0] is the folder entry full path 
            fn_to_add = os.path.join(walk_item[0], file_item)
            dst_zip_archive.write(fn_to_add)

if __name__ == '__main__':
    zf = zipfile.ZipFile('myzip.zip', mode='w')
    add_folder_to_zip('zip_this_folder', zf)
    zf.close()
rob_7cc
  • 797
  • 6
  • 16