12

Why doesn't this unzip all the zip files in a given directory?

find -name "*.zip" -exec unzip {} + \;

Instead I get "caution: filename not matched:" for each matching item

3
  • zip already zipped files?
    – Hannu
    Commented Nov 7, 2022 at 18:42
  • fwiw i find xargs easier to reason about: find -name "*.zip" -print0 | xargs -0 unzip
    – hanshenrik
    Commented Nov 8, 2022 at 21:12
  • just to be clear, you want to ignore .ZIP and .Zip, you only want .zip right? otherwise you probably want -iname instead of -name
    – hanshenrik
    Commented Nov 8, 2022 at 21:14

2 Answers 2

28

Analysis

find -name "*.zip" -exec unzip {} + \;

This command itself is invalid on the level of find, it doesn't even get to a point where unzip runs. A valid -exec action inside a find command is terminated either by + or by \;*, not by both at the same time. Your description matches the behavior of the action terminated by + only, so I assume this was the command you really used:

find -name "*.zip" -exec unzip {} +   # still wrong

Why doesn't this unzip all the zip files in a given directory?

Because unzip expects exactly one zip file. Additional operands are interpreted as "archive members to be processed" (see man 1 unzip), i.e. files you want to extract from the archive. By using -exec unzip {} + you invoke unzip with possibly multiple arguments from the expansion of {}. All arguments but the first are interpreted as archive members to be processed, but you don't want them to be treated as such.


* Strictly: ;. The backslash in \; is just to escape ; in a shell, so the shell does not treat ; as command terminator. The shell strips the backslash and find sees ; as it should.


Solution

Use -exec unzip {} \; instead. This form will replace {} with exactly one pathname. It will spawn more unzip processes than -exec unzip {} + would, but each unzip will get exactly one zip file to process and this is what the tool expects.

find . -type f -name "*.zip" -exec unzip {} \;

I explicitly specified . as the starting path to make the answer portable. Your find apparently uses . by default, but some implementations of find require at least one path in the command line. -type f is in case some directory (or a socket etc.) matched -name "*.zip"; in general it may.


Example

Imagine there are ./foo.zip and ./bar.zip as the only matching files. find -name "*.zip" -exec unzip {} + will execute:

unzip ./foo.zip ./bar.zip

(or unzip ./bar.zip ./foo.zip). This command will try to extract ./bar.zip from ./foo.zip (or the other way around, respectively). This is not what you want to do.

On the other hand find -name "*.zip" -exec unzip {} \; will execute:

unzip ./foo.zip
unzip ./bar.zip

or the same pair of commands in reverse order. This is what you want to do.


Note

The order in the above example is not specified. In practice this answer is basically true:

find will be traversing the directory tree in the order items are stored within the directory entries. […]

In theory find is not required to traverse in this order, it just happens it's a convenient order to implement.

Anyway, in your case if files extracted from one zip collide with files stored in another zip then at some point unzip will ask you what to do. Pay attention to what archive the question is about; do not assume any particular order of archives.

-4

In addition to Kamil's excellent answer, there's a more insidious problem here: the use of double quotes rather than single quotes around *.zipwill allow expansion to happen during execution of the find command.

Consider the following example:

  • You have abc.zip in the . directory
  • There are one or more subdirectories containing files such as def.zip, ghi.zip, etc.

When the find command executes, the shell expands *.zip to be abc.zip effectively making your command execute as find -name abc.zip -exec . . . -- this is probably not what you want.

3
  • 7
    -1. This is not true. Single- or double-quotes prevent * from being expanded by the shell. If the asterisk in *.zip was unquoted then yes, this could be a problem. In the question there is "*.zip", the asterisk is quoted, it's totally OK. Commented Nov 8, 2022 at 16:49
  • 1
    Double quotes allow variables and command substitutions to be expanded, but not wildcards.
    – Barmar
    Commented Nov 9, 2022 at 15:41
  • However, as a matter of style, I recommend using single quotes except for strings that contain variables that should be expanded. So I always put the -file argument in single quotes for clarity.
    – Barmar
    Commented Nov 9, 2022 at 15:43

You must log in to answer this question.

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