Zip – Create Directory If Zip Archive Contains Several Files

zip

If an archive contains several files in the root directory, I'd like to create
a directory named after the file name (without extension). If an archive
contains just a directory, then I'd like to simply extract it. Assume I have
the following archive:

# file: withdir.zip
somedir/alpha
somedir/beta
somedir/gamma

If I extract it in the current directory I'd like to have it simply extracted
(unzip withdir.zip):

somedir/alpha
somedir/beta
somedir/gamma

Now assume the following archive:

# file: nodir.zip
alpha
beta
gamma

When I run unzip nodir.zip I end up cluttering the current directory
with the three files:

alpha
beta
gamma

I'd rather run unzip -d nodir nodir.zip:

nodir/alpha
nodir/beta
nodir/gamma

If I use nautilus and right click on “Extract Here”. It behaves exactly as it
should. But unfortunately I haven't found a command line switch for unzip or
7z which yield the same behaviour. How to achieve that? Are there other tools I can use instead
(no GUI)?

Best Answer

I would do something like this (zsh syntax):

unz() (
  tmp=$(TMPDIR=. mktemp -d -- ${${argv[-1]:t:r}%.tar}.XXXXXX) || exit
  print -r >&2 "Extracting in $tmp"
  cd -- $tmp || exit
  [[ $argv[-1] = /* ]] || argv[-1]=../$argv[-1]
  (set -x; "$@"); ret=$?
  files=(*(ND[1,2]))
  case $#files in
    (0) print -r >&2 "No file created"
        rmdir -v "../$tmp";;
    (1) mv -v -- $files .. && rmdir -v ../$tmp;;
    (*) mv -vT ../$tmp ../$tmp:r;;
  esac && exit $ret
)

That is:

  1. create a directory in anycase
  2. run the command
  3. depending on how many files the command generated:
    • remove that directory (if it didn't create any file)
    • if it created only one file/dir, move it one level up and discard our directory
    • otherwise, attempt to strip the random string from the end of our temp directory.

This way, you can do:

unz unzip foo.zip
unz tar xf foo.tar.gz

It assumes that the last argument to the extracting command is the file to extract. It also assumes GNU tools for the -v options. On non-GNU systems, you can remove those and possibly do the logging by hand. mv -T is also GNU specific, and is to force mv to attempt do a rename only.

Related Question