7

I have 100s of directories and within those I have a few zip files. Now there are images named abc.jpg in those zip files. The zip files may be in any folder or in any subfolder so its difficult to extract them all in one place.

I just want to collect those image files. Is this possible?

1
  • 1
    You can use zip -sf foo.zip | grep abc.jpg to determine if an archive has abc.jpg; that should help. I don't have time to figure out the complete command now, but I'll try later if nobody else has answered Apr 26, 2011 at 13:46

5 Answers 5

6

I once needed something similar to find class files in a bunch of zip files. Here it is:

#!/bin/bash

function process() {
while read line; do
    if [[ "$line" =~ ^Archive:\s*(.*) ]] ; then
        ar="${BASH_REMATCH[1]}"
        #echo "$ar"
    else
        if [[ "$line" =~ \s*([^ ]*abc\.jpg)$ ]] ; then
            echo "${ar}: ${BASH_REMATCH[1]}"
        fi
    fi
done
}


find . -iname '*.zip' -exec unzip -l '{}' \; | process

Now you only need to add one line to extract the files and maybe move them. I'm not sure exactly what you want to do, so I'll leave that to you.

4

If your unix variant supports FUSE (Linux, *BSD, OSX, Solaris all do), mount AVFS to access archives transparently. The command mountavfs creates a view of the whole filesystem, rooted at ~/.avfs, in which archive files have an associated directory that contains the directories and files in the archive. For example, if you have foo.zip in the current directory, then the following command is roughly equivalent to unzip -l foo.zip:

mountavfs    # needs to be done once and for all
find ~/.avfs$PWD/foo.zip\# -ls

So, to loop over all images contained in a zip file under the current directory and copy them to /destination/directory (with a prompt in case of clash):

find ~/.avfs"$PWD" -name '*.zip' -exec sh -c '
    find "${0}#" -name "*.jpg" -exec cp -ip {} "$1" \;
' {} /destination/directory \;

In zsh:

cp -ip ~/.avfs$PWD/**/*.zip(e\''REPLY=($REPLY\#/**/*.jpg(N))'\') /destination/directory

Deconstruction: ~/.avfs$PWD/**/*.zip expands to the AVFS view of the zip files under the current directory. The glob qualifier e is used to modify the output of the glob: …/*.zip(e\''REPLY=$REPLY\#'\') would just append a # to each match. REPLY=($REPLY\#/**/*.jpg(N)) transforms each match into the array of .jpg files in the .zip# directory.

3
  • 1
    nice solution, but FUSE? that's a little bit overkill, isn't it?
    – Kim
    Apr 26, 2011 at 14:38
  • 2
    @Kim: Why should I cripple myself not to use FUSE if it's available? Apr 26, 2011 at 14:53
  • 1
    some reasons not to use FUSE if you don't have to: portability (some OSs don't have FUSE), maintainability (not everybody knows FUSE)
    – Kim
    Apr 26, 2011 at 14:59
1

I assume you have a new version of Bash, so you should be able to use this:

shopt -s globstar
for path in topdir/**/*.zip
do
    unzip "$path" '.*abc.jpg'
done
1

Similar to Kims answer but slightly modified. Just use sed:

find . -name *.zip -exec unzip -l '{}' \; | sed -n -e '/^Archive/ {h}' -e '/abc.jpg$/ {x;p;x;}'
-1

Let's do this! Tragically, existing answers are deficient in various obvious ways – including those both here and at a popular duplicate.

The accepted answer, for example, is Bash-specific (that's bad) and hardcodes the desired search pattern into a one-off 10-line shell function (that's even badder). The next most upvoted answer leverages FUSE-based pseudo-filesystems (that's patently insane). Likewise, the most upvoted answer at the aforementioned duplicate yields ambiguous, non-human-readable output (just... ugh).

I am Jack's wizened disapproval.

Working Code or It Didn't Happen

A new contender has entered the ring:

# str find_in_zip(str regex, str zip_filename1, ...)
#
# Find all paths contained in any zip-formatted archives with the passed
# filenames such that the relative pathnames of these paths in these
# archives match the passed extended regular expression.
function find_in_zip() {
    (( $# >= 2 )) || {
        echo 'Expected one extended regular expression and one or more zip filenames.' 1>&2
        return 1
    }

    # Localize and remove the passed regex from the argument list.
    local regex="${1}" zip_filename
    shift

    # For each passed zip filename...
    for zip_filename in "${@}"; do
        # Print the name of this filename for disambiguity.
        echo "${zip_filename}:"

        # Print all paths in this file matching this regex.
        command unzip -l "${zip_filename}" |
            command grep --extended-regexp --color=always "${regex}"
    # Page the above output for readability.
    done | less --RAW-CONTROL-CHARS
}

For usability, this function is called with the exact same signature as grep. Namely, this function first accepts the regular expression to be searched for and then a variadic sequence of one or more zip filenames.

Likewise, this function has been tested under both Bash and zsh. Add the above code to either ~/.bashrc or ~/.zshrc and great zipfile glory shall be yours, ideally with set -e enabled for sanity and strictness.

Examples or It Didn't Happen

To demonstrate, let's find the set of all classes embedded in I2P JAR files installed under Gentoo Linux whose names begin with exactly seven uppercase characters followed by one lowercase character – just 'cause:

$ find_in_zip '/[A-Z]{7}[a-z]' /usr/share/i2p/lib/*.jar
/usr/share/i2p/lib/addressbook.jar:
/usr/share/i2p/lib/BOB.jar:
/usr/share/i2p/lib/commons-el.jar:
/usr/share/i2p/lib/desktopgui.jar:
/usr/share/i2p/lib/i2p.jar:
      568  01-16-2020 00:20   freenet/support/CPUInformation/AMDCPUInfo.class
      236  01-16-2020 00:20   freenet/support/CPUInformation/VIACPUInfo.class
/usr/share/i2p/lib/i2psnark.jar:
/usr/share/i2p/lib/i2ptunnel.jar:
/usr/share/i2p/lib/jasper-compiler.jar:
/usr/share/i2p/lib/jasper-runtime.jar:
/usr/share/i2p/lib/jetty-continuation.jar:
/usr/share/i2p/lib/jetty-deploy.jar:
/usr/share/i2p/lib/jetty-http.jar:
/usr/share/i2p/lib/jetty-i2p.jar:
/usr/share/i2p/lib/jetty-io.jar:
/usr/share/i2p/lib/jetty-java5-threadpool.jar:
/usr/share/i2p/lib/jetty-rewrite-handler.jar:
/usr/share/i2p/lib/jetty-security.jar:
/usr/share/i2p/lib/jetty-servlet.jar:
/usr/share/i2p/lib/jetty-servlets.jar:
/usr/share/i2p/lib/jetty-sslengine.jar:
/usr/share/i2p/lib/jetty-start.jar:
/usr/share/i2p/lib/jetty-util.jar:
/usr/share/i2p/lib/jetty-webapp.jar:
/usr/share/i2p/lib/jetty-xml.jar:
/usr/share/i2p/lib/jstl.jar:
/usr/share/i2p/lib/mstreaming.jar:
/usr/share/i2p/lib/org.mortbay.jetty.jar:
/usr/share/i2p/lib/org.mortbay.jmx.jar:
/usr/share/i2p/lib/routerconsole.jar:
/usr/share/i2p/lib/router.jar:
     5598  01-16-2020 00:20   org/cybergarage/upnp/ssdp/HTTPMUSocket.class
/usr/share/i2p/lib/sam.jar:
/usr/share/i2p/lib/standard.jar:
/usr/share/i2p/lib/streaming.jar:
/usr/share/i2p/lib/systray.jar:

You... probably wouldn't want to do that by hand.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .