96

The unzip command doesn't have an option for recursively unzipping archives.

If I have the following directory structure and archives:

/Mother/Loving.zip
/Scurvy/Sea Dogs.zip
/Scurvy/Cures/Limes.zip

And I want to unzip all of the archives into directories with the same name as each archive:

/Mother/Loving/1.txt
/Mother/Loving.zip
/Scurvy/Sea Dogs/2.txt
/Scurvy/Sea Dogs.zip
/Scurvy/Cures/Limes/3.txt
/Scurvy/Cures/Limes.zip

What command or commands would I issue?

It's important that this doesn't choke on filenames that have spaces in them.

chuckrector
  • 1,471
  • 1
  • 12
  • 9
  • How about long file paths... beyond 260 characters...: https://superuser.com/a/1263234/439537 – Andrew Oct 28 '17 at 06:13

11 Answers11

169

If you want to extract the files to the respective folder you can try this

find . -name "*.zip" | while read filename; do unzip -o -d "`dirname "$filename"`" "$filename"; done;

A multi-processed version for systems that can handle high I/O:

find . -name "*.zip" | xargs -P 5 -I fileName sh -c 'unzip -o -d "$(dirname "fileName")/$(basename -s .zip "fileName")" "fileName"'
Community
  • 1
  • 1
Vivek Thomas
  • 1,730
  • 1
  • 11
  • 2
  • 2
    This is the proper way of doing it, since it preserves the full directory structure! – kynan Nov 22 '14 at 12:36
  • 12
    __NOTE__ - this doesn't extract the files into directories with the same name as the archive, as requested. – Emil Laine May 01 '16 at 17:12
  • The fact that this does not unzip to separate folders means it doesn't fully answer the question asked. Also, if multiple zip files in a given folder contain files with the same name, e.g. `results.log` this will cause issues when you try to unzip multiple copies of the same file into one folder. I'm trying this on Cygwin. It looks like the `-d "'dirname...` part is supposed to handle this but it's not working for me. – SSilk May 25 '17 at 20:00
  • 3
    For some reason, the first example had the same problem for me as for emlai, all unzipped files went in the directory the zip file was in. However, the second one, the multithreaded method, not only worked much faster but also put each zip file's files in a directory named after the zip file. Perfect! – MoTLD Aug 29 '17 at 23:09
  • A small addition: to delete the zip files if successfully unzipped, add && rm "fileName" immediately before the last ', as in find . -name "*.zip" | xargs -P 5 -I fileName sh -c 'unzip -o -d "$(dirname "fileName")/$(basename -s .zip "fileName")" "fileName" && rm "fileName"' – MoTLD Aug 29 '17 at 23:20
59

A solution that correctly handles all file names (including newlines) and extracts into a directory that is at the same location as the file, just with the extension removed:

find . -iname '*.zip' -exec sh -c 'unzip -o -d "${0%.*}" "$0"' '{}' ';'

Note that you can easily make it handle more file types (such as .jar) by adding them using -o, e.g.:

find . '(' -iname '*.zip' -o -iname '*.jar' ')' -exec ...
robinst
  • 30,027
  • 10
  • 102
  • 108
40

Here's one solution that extracts all zip files to the working directory and involves the find command and a while loop:

find . -name "*.zip" | while read filename; do unzip -o -d "`basename -s .zip "$filename"`" "$filename"; done;
TankorSmash
  • 12,186
  • 6
  • 68
  • 106
chuckrector
  • 1,471
  • 1
  • 12
  • 9
  • 8
    **NOTE** - this extract all zip files to the **working directory!** while you requested a relative unzip. see [Vivek's answer](http://stackoverflow.com/a/2318189/3191896) – Jossef Harush Kadouri Mar 04 '15 at 08:11
  • Hmm, it doesn't appear to extract them all to the working directory AFAICT. I'm using UnZip 5.52 from OS X El Capitan 10.11.6. Results seem to be identical to Vivek's. Accepting his answer though, since it also shows how to take advantage of multiple cores! – chuckrector Oct 09 '16 at 00:36
4

You could use find along with the -exec flag in a single command line to do the job

find . -name "*.zip" -exec unzip {} \;
Jahangir
  • 685
  • 6
  • 13
  • 2
    This unzips everything into the current directory, rather than relative to each subdirectory. It also doesn't unzip into a directory with the same name as each archive. – chuckrector Sep 20 '08 at 12:31
  • I assume that getting the -d right is left as an exercise for the reader. That reader needs to note that -exec only allows one use of {} in the command - get around this by calling sh and assigning {} to a variable. – Steve Jessop Sep 20 '08 at 12:37
  • Also note that -execdir is sometimes preferable to -exec. In this case I don't think it will matter. – Steve Jessop Sep 20 '08 at 12:39
  • My find (GNU findutils 4.4.0) lets me use {} more than once... cloud@thunder:~/tmp/files$ find . -exec echo {} {} \; . . ./a ./a ./b ./b ./c ./c ./d ./d ./e ./e ./f ./f ./g ./g – Sam Reynolds Sep 20 '08 at 13:19
  • See [my answer](http://stackoverflow.com/a/22384233/305973), which uses `-exec` including `-d`. Note that you have to take care when calling `sh` to make sure that an evil file name like `"; rm -rf /;` is not executed. – robinst Mar 13 '14 at 16:14
4

This works perfectly as we want:

Unzip files:

find . -name "*.zip" | xargs -P 5 -I FILENAME sh -c 'unzip -o -d "$(dirname "FILENAME")" "FILENAME"'

Above command does not create duplicate directories.

Remove all zip files:

find . -depth -name '*.zip' -exec rm {} \;
Prabhav
  • 447
  • 3
  • 17
2

Something like gunzip using the -r flag?....

Travel the directory structure recursively. If any of the file names specified on the command line are directories, gzip will descend into the directory and compress all the files it finds there (or decompress them in the case of gunzip ).

http://www.computerhope.com/unix/gzip.htm

2

If you're using cygwin, the syntax is slightly different for the basename command.

find . -name "*.zip" | while read filename; do unzip -o -d "`basename "$filename" .zip`" "$filename"; done;
1

I realise this is very old, but it was among the first hits on Google when I was looking for a solution to something similar, so I'll post what I did here. My scenario is slightly different as I basically just wanted to fully explode a jar, along with all jars contained within it, so I wrote the following bash functions:

function explode {
    local target="$1"
    echo "Exploding $target."
    if [ -f "$target" ] ; then
        explodeFile "$target"
    elif [ -d "$target" ] ; then
        while [ "$(find "$target" -type f -regextype posix-egrep -iregex ".*\.(zip|jar|ear|war|sar)")" != "" ] ; do
            find "$target" -type f -regextype posix-egrep -iregex ".*\.(zip|jar|ear|war|sar)" -exec bash -c 'source "<file-where-this-function-is-stored>" ; explode "{}"' \;
        done
    else
        echo "Could not find $target."
    fi
}

function explodeFile {
    local target="$1"
    echo "Exploding file $target."
    mv "$target" "$target.tmp"
    unzip -q "$target.tmp" -d "$target"
    rm "$target.tmp"
}

Note the <file-where-this-function-is-stored> which is needed if you're storing this in a file that is not read for a non-interactive shell as I happened to be. If you're storing the functions in a file loaded on non-interactive shells (e.g., .bashrc I believe) you can drop the whole source statement. Hopefully this will help someone.

A little warning - explodeFile also deletes the ziped file, you can of course change that by commenting out the last line.

Vala
  • 5,628
  • 1
  • 29
  • 55
0

Another interesting solution would be:

DESTINY=[Give the output that you intend]

# Don't forget to change from .ZIP to .zip.
# In my case the files were in .ZIP.
# The echo were for debug purpose.

find . -name "*.ZIP" | while read filename; do
ADDRESS=$filename
#echo "Address: $ADDRESS"
BASENAME=`basename $filename .ZIP`
#echo "Basename: $BASENAME"
unzip -d "$DESTINY$BASENAME" "$ADDRESS";
done;
Prometheus
  • 523
  • 7
  • 19
0

You can also loop through each zip file creating each folder and unzip the zip file.

for zipfile in *.zip; do
  mkdir "${zipfile%.*}"
  unzip "$zipfile" -d "${zipfile%.*}"
done
Hongbo Miao
  • 45,290
  • 60
  • 174
  • 267
-3

this works for me

def unzip(zip_file, path_to_extract):
    """
    Decompress zip archives recursively
    Args:
        zip_file: name of zip archive
        path_to_extract: folder where the files will be extracted
    """
    try:
        if is_zipfile(zip_file):
            parent_file = ZipFile(zip_file)
            parent_file.extractall(path_to_extract)
            for file_inside in parent_file.namelist():
                if is_zipfile(os.path.join(os.getcwd(),file_inside)):
                    unzip(file_inside,path_to_extract)
            os.remove(f"{zip_file}")
    except Exception as e:
        print(e)
greybeard
  • 2,249
  • 8
  • 30
  • 66
  • 1
    Even with the question neither tagged [tag:shell] nor [tag:python], I don't see why a Python answer would be welcome. It *might* become useful if it descended the directory tree as shown in the question instead of unzipping zipped zips. – greybeard Jun 28 '21 at 07:30