8

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)?

4 Answers 4

7

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.

3

As unzip and 7z both have a switch to output to a given directory, you can create a simple script, that checks, how many files are in the root of the archive and then adds the switch if needed.

With 7z this is easier, as it has the -slt switch, which makes the output more machine-readable. For every file in the zip, the output will contain a Path = ... line. Files in the root should not contain any / in the value.

A simple script I wrote to unpack files that way:

#!/bin/sh
for archive in "$@"; do
    # count files in root dir
    rootfiles=$(7z l -slt "${archive}" | grep -c 'Path = [^/]*$')

    # add -o switch, if more than one file in root
    # checks for >2, because path of the zip file itself is listed too
    if [ $rootfiles -gt 2 ]; then
        filename=${archive##*/}
        rootname=${filename%.*}
        opt="-o${rootname}"
    fi

    #unpack
    7z x "${opt}" "${archive}"
done

Edit: script now works with zip too

3
  • This script doesn't work as expected. It creates a directory withdir for the archive withdir.zip although it contains just one directory in the root because grep -c "Folder = -" returns 3.
    – Marco
    Commented Feb 7, 2013 at 11:24
  • ah, Folder seems to be different for zip files. I'll look for a fix
    – crater2150
    Commented Feb 7, 2013 at 11:26
  • @Marco: I changed the script and tested it with zip files, it now only creates the folder when needed
    – crater2150
    Commented Feb 7, 2013 at 11:32
3

patool handles different kinds of archives and creates a subdirectory in case the archive contains multiple files to prevent cluttering the working directory with the extracted files.

Create archive

patool create archive.zip somedirectory

Extract archive

patool extract archive.zip

To obtain a list of the supported formats, use patool formats.

1

You could also use unar, which creates a containing directory by default when there are two or more top-level files:

$ touch 1 2
$ zip a.zip 1 2
  adding: 1 (stored 0%)
  adding: 2 (stored 0%)
$ unzip -l a.zip
Archive:  a.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
        0  04-29-14 10:56   1
        0  04-29-14 10:56   2
 --------                   -------
        0                   2 files
$ unar a.zip
a.zip: Zip
  1  (0 B)... OK.
  2  (0 B)... OK.
Successfully extracted to "a".
$ ls a
1 2

Add -d to always create a containing directory.

You must log in to answer this question.

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