Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I have a script that extracts a tar.gz-file to a speificed 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?

share|improve this question

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

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

add comment

2 Answers

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)

share|improve this answer
add comment

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
share|improve this answer
    
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
    
TIL about mktemp. Thanks! –  Mathias Bynens Mar 29 '13 at 11:13
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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