Python zipfile removes execute permissions from binaries
<p>Not sure why this is happening, but when I run unzip a file (e.g. apache-groovy-binary-2.4.7.zip) at the command line...  </p>

<ul>
<li>directories are rwx-r-xr-x </li>
<li>files are rwxr-xr-x or rw-r--r--</li>
</ul>

<p>But when I run zipfile.extractall from a Python 2.7 script on the same file... </p>

<ul>
<li>directories are rwx-r-x--- </li>
<li>files are all rw-r---- - even the ones that should be executables as per above.</li>
</ul>

<p>My umask setting is 0027 - this partly explains what's going on, but why is the executable bit being removed from all files?</p>

<p>What's the easiest fix to get Python adopting similar behaviour to the command-line version (apart from shelling out, of course!)?</p>

<p>The reason for this can be found in the <code>_extract_member()</code> method in <code>zipfile.py</code>, it only calls <code>shutil.copyfileobj()</code> which will write the output file without any execute bits.</p>

<p>The easiest way to solve this is by subclassing <code>ZipFile</code> and changing <code>extract()</code> (or patching in an extended version. By default it is:</p>

<pre><code>def extract(self, member, path=None, pwd=None):
    """Extract a member from the archive to the current working directory,
       using its full name. Its file information is extracted as accurately
       as possible. `member' may be a filename or a ZipInfo object. You can
       specify a different directory using `path'.
    """
    if not isinstance(member, ZipInfo):
        member = self.getinfo(member)

    if path is None:
        path = os.getcwd()

    return self._extract_member(member, path, pwd)
</code></pre>

<p>This last line should be changed to actually set the mode based on the original attributes. You can do it this way:</p>

<pre><code>import os
from zipfile import ZipFile, ZipInfo

class MyZipFile(ZipFile):

    def extract(self, member, path=None, pwd=None):
        if not isinstance(member, ZipInfo):
            member = self.getinfo(member)

        if path is None:
            path = os.getcwd()

        ret_val = self._extract_member(member, path, pwd)
        attr = member.external_attr &gt;&gt; 16
        os.chmod(ret_val, attr)
        return ret_val


with MyZipFile('test.zip') as zfp:
    zfp.extractall()
</code></pre>

<p>(The above is based on Python 3.5 and assumes the zipfile is called <code>test.zip</code>)</p>

python python-2.7