Convert a Windows-created ZIP to Linux (internal paths issue)

I think something went wrong with the creation of the zip file, because when I create a zip file on Windows is has (portable) forward slashes:

zip.exe -r pip pip
updating: pip/ (244 bytes security) (stored 0%)
  adding: pip/pip.log (164 bytes security) (deflated 66%)

But now that you have the files with file names that contain "paths" with backslashes, you can run the following program in unzip_dir:

#! /usr/bin/env python

# already created directories, walk works topdown, so a child dir
# never creates a directory if there is a parent dir with a file.
made_dirs = set()

for root, dir_names, file_names in os.walk('.'):
    for file_name in file_names:
        if '\\' not in file_name:
            continue
        alt_file_name = file_name.replace('\\', '/')
        if alt_file_name.startswith('/'):
            alt_file_name = alt_file_name[1:]  # cut of starting dir separator
        alt_dir_name, alt_base_name = alt_file_name.rsplit('/', 1)
        print 'alt_dir', alt_dir_name
        full_dir_name = os.path.join(root, alt_dir_name)
        if full_dir_name not in made_dirs:
            os.makedirs(full_dir_name)  # only create if not done yet
            made_dirs.add(full_dir_name)
        os.rename(os.path.join(root, file_name),
                  os.path.join(root, alt_file_name))

This handles files in any directory under the directory from where the program is started. Given the problem that you describe, the unzip_dir probably doesn't have any subdirectories to start with, and the program could just walk over the files in the current directory only.


Use 7z rn to rename the files within the archive so that they have a forward slash. Then when you extract the archive, directories will be created.

To rename the files, list the paths of the files within the archive containing slashes, and generate a list of replacement strings that change the backslash to a slash using awk, for example.

7z rn windows.zip $(7z l windows.zip | grep '\\' | awk '{ print $6, gensub(/\\/, "/", "g", $6); }' | paste -s)

This is just an update of @anton's answer which includes fixes by @madmuffin (FileExistsError: [Errno 17] File exists and missing os module import), a fix for python 3 (SyntaxError: Missing parentheses in call to 'print') and a fix for the missing errno module import (NameError: name 'errno' is not defined).

#! /usr/bin/env python

import os
import errno

# already created directories, walk works topdown, so a child dir
# never creates a directory if there is a parent dir with a file.
made_dirs = set()

for root, dir_names, file_names in os.walk('.'):
    for file_name in file_names:
        if '\\' not in file_name:
            continue
        alt_file_name = file_name.replace('\\', '/')
        if alt_file_name.startswith('/'):
            alt_file_name = alt_file_name[1:]  # cut of starting dir separator
        alt_dir_name, alt_base_name = alt_file_name.rsplit('/', 1)
        print('alt_dir', alt_dir_name)
        full_dir_name = os.path.join(root, alt_dir_name)
        if full_dir_name not in made_dirs:
            try:
                os.makedirs(full_dir_name)
            except OSError as exc:
                if exc.errno == errno.EEXIST and os.path.isdir(full_dir_name):
                    # the pass already exists and is a folder, let's just ignore it
                    pass
                else:
                    raise 
            made_dirs.add(full_dir_name)
        os.rename(os.path.join(root, file_name),
                  os.path.join(root, alt_file_name))

Tags:

Filenames

Zip