43

I have a script that extracts a tar.gz-file to a specified subdirectory mysubfolder:

mkdir mysubfolder; tar --extract --file=sourcefile.tar.gz --strip-components=1 --directory=mysubfolder;

Is there any equivalent way of doing this with a zip-file?

migrated from stackoverflow.com Dec 12 '12 at 14:05

This question came from our site for professional and enthusiast programmers.

  • 2
    Just use bsdtar – drizzt Aug 10 '15 at 17:01
24

As Mathias said, unzip has no such option, but a one-liner bash script can do the job.

Problem is: the best approach depends on your archive layout. A solution that assumes a single top-level dir will fail miserably if the content is directly in the archive root (think about /a/foo /b/foo /foo and the chaos of stripping /a and /b).

And the same fail happens with tar --strip-component. There is no one-size-fits-all solution.

So, to strip the root dir, assuming there is one (and only one):

unzip -d "$dest" "$zip" && f=("$dest"/*) && mv "$dest"/*/* "$dest" && rmdir "${f[@]}"

Just make sure second-level files/dirs do not have the same name of the top-level parent (for example, /foo/foo). But /foo/bar/foo and /foo/bar/bar are ok. If they do, or you just want to be safe, you can use a temp dir for extraction:

temp=$(mktemp -d) && unzip -d "$temp" "$zip" && mkdir -p "$dest" &&
mv "$temp"/*/* "$dest" && rmdir "$temp"/* "$temp"

If you're using Bash, you can test if top level is a single dir or not using:

f=("$temp"/*); (( ${#f[@]} == 1 )) && [[ -d "${f[0]}" ]] && echo "Single dir!"

Speaking of Bash, you should turn on dotglob to include hidden files, and you can wrap everything in a single, handy function:

unzip-strip() (
    local zip=$1
    local dest=${2:-.}
    local temp=$(mktemp -d) && unzip -d "$temp" "$zip" && mkdir -p "$dest" &&
    shopt -s dotglob && local f=("$temp"/*) &&
    if (( ${#f[@]} == 1 )) && [[ -d "${f[0]}" ]] ; then
        mv "$temp"/*/* "$dest"
    else
        mv "$temp"/* "$dest"
    fi && rmdir "$temp"/* "$temp"
)

Now put that in your ~/.bashrc or ~/.profile and you'll never have to worry about it again. Simply use as:

unzip-strip sourcefile.zip mysubfolder

(notice it will automatically create mysubfolder for you if it does not exist)

  • This will not unzip into an existing directory structure as I'd hoped (I tried to use . in place of mysubfolder). I ended up just unzipping (unzip zip-with-top-dir.zip) and then copying (cp -rv extracted-top-zip-dir/* .). – catgofire Oct 25 '16 at 21:14
2

I couldn’t find such an option in the manual pages for unzip, so I’m afraid this is impossible. :(

However, (depending on the situation) you could work around it. For example, if you’re sure the only top-level directory in the zip file is named foo- followed by a version number, you could do something like this:

cd /tmp
unzip /path/to/file.zip
cd foo-*
cp -r . /path/to/destination/folder
  • Nice approach, but a bit incomplete: you will still have foo* dir with the full extracted content. – MestreLion Mar 28 '13 at 5:44
  • Yes, I didn’t add rm -rf foo-* on purpose as that’s potentially dangerous. What if there already was a folder named foo-bar? Note that the extraction is being done within the /tmp folder, which gets emptied automatically every now and then. – Mathias Bynens Mar 28 '13 at 10:59
  • That's why I chained operations using &&: a given step only happens if the previous step was successful, so the last one (the rm) only runs if all steps completed with no error. – MestreLion Mar 28 '13 at 15:21
  • 1
    That's also why one should never user /tmp/some-hardcoded-folder-name as a temp folder, but instead should use mktemp for that: it guarantees there will be no such existing folder. Check my answer below. – MestreLion Mar 28 '13 at 15:23
0

You can use -j to junk paths (do not make directories).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.