Create a dedicated folder for every zip files in a directory and extract zip files
Asked Answered
O

7

64

If I choose a zip file and right click "extract here" a folder with the zip filename is created and the entire content of the zip file is extracted into it.

However, I would like to convert several zip files via shell. But when I do

unzip filename.zip

the folder "filename" is not created but all the files are extracted into the current directory.

I have looked at the parameters but there is no such parameter. I also tried

for zipfile in \*.zip; do mkdir $zipfile; unzip $zipfile -d $zipfile/; done

but the .zip extension of the 2. $zipfile and 4. $zipfile have to be removed with sed. If I do

for zipfile in \*.zip; do mkdir sed 's/\.zip//i' $zipfile; unzip $zipfile -d sed 's/\.zip//i' $zipfile/; done 

it is not working.

How do I replace the .zip extension of $zipfile properly?

Is there an easier way than a shell script?

Oxycephaly answered 12/11, 2011 at 22:1 Comment(0)
T
64

"extract here" is merely a feature of whatever unzip wrapper you are using. unzip will only extract what actually is in the archive. There is probably no simpler way than a shell script. But sed, awk etc. are not needed for this if you have a POSIX-compliant shell:

for f in *.zip; do unzip -d "${f%*.zip}" "$f"; done

(You MUST NOT escape the * or pathname expansion will not take place.) Be aware that if the ZIP archive contains a directory, such as with Eclipse archives (which always contain eclipse/), you would end up with ./eclipse*/eclipse/eclipse.ini in any case. Add echo before unzip for a dry run.

Thumbstall answered 12/11, 2011 at 22:31 Comment(14)
@jack, THIS answer should be the accept answer. You should NOT USE ls output for anything. ls is a tool for interactively looking at directory metadata. Any attempts at parsing ls output with code are broken. Globs are much more simple AND correct: for file in *.zip. Read Parsing lsBaer
Note: could you improve your answer (which absolutely answers the original question of how to extract multiple zip files in dedicated fodlers) with the case "how to create folder to extract a single zip file in it?"Keijo
Please why this work alias unzipd='function uzd() { args=("$@") for f in "${args[@]}"; do echo "------- $f ----------" unzip $f -d "${f%*.zip}" done } uzd ' and not alias unzipd=' args=("$@") for f in "${args[@]}"; do echo "------- $f ----------" unzip $f -d "${f%*.zip}" done ' (Why aliases can't handle it ?) erro: syntax error near token //remark: they are in multilinesFurfuraceous
and as sited here https://mcmap.net/q/11586/-make-a-bash-alias-that-takes-a-parameter, it say alias don't take parameters directly and function does, but i always use things like gipsh="git push origin $@". What's the difference about $@ ?Furfuraceous
i have the answer (half), i didn't have a good conception of how alias work, it just work like an include in c/c++, whatever the alias name hold, will be copied in it's place, and if $@, $1 .. is written to the end (or any where), it get omited, so ex: alias lsg='ls | grep ' and then lsg zip, that will be the equivalent of ls | grep zip. And alias ech='echo "$1" ' and ech ok yes again | you expect that ok only show, but the result is like "$1" doesn't exist. It's just like variable are ignored. If any one can explain more it will be great. Ty a lot.Furfuraceous
@MohamedAllal Please ask a new question.Thumbstall
@Keijo Simply replace the first *.zip with the path of the ZIP archive that you want to extract :)Thumbstall
The replace in "${f%*.zip}" deletes the shortest regex pattern (tldp.org/LDP/abs/html/string-manipulation.html). a) the leading * doesn't make sense in a regex and seems to be silently ignored and b) regex is overkill for a fixed string like .zip. In Bash you can use "${f:0:(-4)}" to trim the last four characters.Cresida
@Cresida You need to read more carefully: Regular expressions are not involved here (and it is not actually Bash, but works in any POSIX-compliant shell [AISB]). See “Substring removal” in that Bash tutorial, or pubs.opengroup.org/onlinepubs/9699919799/utilities/… in the POSIX.1-2008 standard.Thumbstall
@Cresida Also, your approach does not work when the shell is not a Bash, or not a shell from which Bash borrowed this feature, or the shell is running in a POSIX-compliant mode, as your suggestion is using a feature that is not POSIX-compliant.Thumbstall
@Thumbstall So it's pattern matching rather than full regex. But you're still using a "zero or more characters" glob on something that performs "shortest match", which is potentially wasteful because what you actually want is a fixed string. myvar=something.zip; echo ${myvar%.zip}; echo ${myvar%*.zip} gives exactly the same output, so the asterisk is pointless. And yes, my suggestion works in Bash and I made that clear by specifically saying "In Bash you can use…". The for loop guarantees last 4 chars are ".zip". Not everyone needs pure POSIX, and that's fine when it's flagged as such.Cresida
@Cresida It is not logical to insist on a less compatible solution when a more compatible one is readily available. I am going to look into your suggestion of omitting the * in the pattern, as there might have been a reason for including it.Thumbstall
@Thumbstall Personally, I think that "logical" depends on whether you're prioritising efficiency or applicability. If I'm writing Python then I might use some new 3.10 features because they're helpful or better, even though I'm then reducing the runtimes that it'll work with. If I control the environment then that's a trade-off I can make. If backwards/cross-compatibility is important then great. If not then it can help to be aware of the options.Cresida
@Cresida Once again: I suggested a solution that works with all POSIX-compliant shells. If you want to suggest a solution that only works with $YOUR_FAVORITE_SHELL, then go ahead and post your own answer.Thumbstall
A
96

unzip file.zip -d xxx will extract files to directory xxx, and xxx will be created if it is not there. You can check the man page for details.

The awk line below should do the job:

ls *.zip|awk -F'.zip' '{print "unzip "$0" -d "$1}'|sh

See the test below,

note that I removed |sh at the end, since my zips are fake archives; I just want to show the generated command line here.

kent$  ls -l
total 0
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 001.zip
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 002.zip
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 003.zip
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 004.zip
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 005.zip
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 006.zip
-rw-r--r-- 1 kent kent 0 Nov 12 23:10 007.zip

kent$  ls *.zip|awk -F'.zip' '{print "unzip "$0" -d "$1}'
unzip 001.zip -d 001
unzip 002.zip -d 002
unzip 003.zip -d 003
unzip 004.zip -d 004
unzip 005.zip -d 005
unzip 006.zip -d 006
unzip 007.zip -d 007
Asphaltite answered 12/11, 2011 at 22:17 Comment(6)
But what I don't understand is why $0 contains the complete file name and $1 not the ".zip" extension?Oxycephaly
@jack I defined ".zip" as the FS (Field Separator). So 001.zip will be separated to two parts "001" and "". $1 is the 1st field. and $0 is the whole line which in this case is "001.zip" in awk, the field is counted from 1, not 0.Asphaltite
But if the xxx is nested-directory path, it will fail and issue "cannot create extraction directory" message. Of course I can write an shell and check if the intermediate directory exists, then create it in advance) but I wish do it in one go with only unzip command.Inez
This command is great, but it will not work if there are spaces in the filename. To account for spaces, you'll need to execute ls *.zip | awk -F'.zip' '{print "unzip \""$0"\" -d \""$1"\""}' | shHoudini
One isn't supposed to parse ls output. Won't work on my system because ls has -lah enabled by default. ls is an interactive tool. Use pathname expansion.Amitosis
DO NOT USE ls output for anything. ls is a tool for interactively looking at directory metadata. Any attempts at parsing ls output with code are broken. Globs are much more simple AND correct: for file in *.txt. Read mywiki.wooledge.org/ParsingLsBaer
T
64

"extract here" is merely a feature of whatever unzip wrapper you are using. unzip will only extract what actually is in the archive. There is probably no simpler way than a shell script. But sed, awk etc. are not needed for this if you have a POSIX-compliant shell:

for f in *.zip; do unzip -d "${f%*.zip}" "$f"; done

(You MUST NOT escape the * or pathname expansion will not take place.) Be aware that if the ZIP archive contains a directory, such as with Eclipse archives (which always contain eclipse/), you would end up with ./eclipse*/eclipse/eclipse.ini in any case. Add echo before unzip for a dry run.

Thumbstall answered 12/11, 2011 at 22:31 Comment(14)
@jack, THIS answer should be the accept answer. You should NOT USE ls output for anything. ls is a tool for interactively looking at directory metadata. Any attempts at parsing ls output with code are broken. Globs are much more simple AND correct: for file in *.zip. Read Parsing lsBaer
Note: could you improve your answer (which absolutely answers the original question of how to extract multiple zip files in dedicated fodlers) with the case "how to create folder to extract a single zip file in it?"Keijo
Please why this work alias unzipd='function uzd() { args=("$@") for f in "${args[@]}"; do echo "------- $f ----------" unzip $f -d "${f%*.zip}" done } uzd ' and not alias unzipd=' args=("$@") for f in "${args[@]}"; do echo "------- $f ----------" unzip $f -d "${f%*.zip}" done ' (Why aliases can't handle it ?) erro: syntax error near token //remark: they are in multilinesFurfuraceous
and as sited here https://mcmap.net/q/11586/-make-a-bash-alias-that-takes-a-parameter, it say alias don't take parameters directly and function does, but i always use things like gipsh="git push origin $@". What's the difference about $@ ?Furfuraceous
i have the answer (half), i didn't have a good conception of how alias work, it just work like an include in c/c++, whatever the alias name hold, will be copied in it's place, and if $@, $1 .. is written to the end (or any where), it get omited, so ex: alias lsg='ls | grep ' and then lsg zip, that will be the equivalent of ls | grep zip. And alias ech='echo "$1" ' and ech ok yes again | you expect that ok only show, but the result is like "$1" doesn't exist. It's just like variable are ignored. If any one can explain more it will be great. Ty a lot.Furfuraceous
@MohamedAllal Please ask a new question.Thumbstall
@Keijo Simply replace the first *.zip with the path of the ZIP archive that you want to extract :)Thumbstall
The replace in "${f%*.zip}" deletes the shortest regex pattern (tldp.org/LDP/abs/html/string-manipulation.html). a) the leading * doesn't make sense in a regex and seems to be silently ignored and b) regex is overkill for a fixed string like .zip. In Bash you can use "${f:0:(-4)}" to trim the last four characters.Cresida
@Cresida You need to read more carefully: Regular expressions are not involved here (and it is not actually Bash, but works in any POSIX-compliant shell [AISB]). See “Substring removal” in that Bash tutorial, or pubs.opengroup.org/onlinepubs/9699919799/utilities/… in the POSIX.1-2008 standard.Thumbstall
@Cresida Also, your approach does not work when the shell is not a Bash, or not a shell from which Bash borrowed this feature, or the shell is running in a POSIX-compliant mode, as your suggestion is using a feature that is not POSIX-compliant.Thumbstall
@Thumbstall So it's pattern matching rather than full regex. But you're still using a "zero or more characters" glob on something that performs "shortest match", which is potentially wasteful because what you actually want is a fixed string. myvar=something.zip; echo ${myvar%.zip}; echo ${myvar%*.zip} gives exactly the same output, so the asterisk is pointless. And yes, my suggestion works in Bash and I made that clear by specifically saying "In Bash you can use…". The for loop guarantees last 4 chars are ".zip". Not everyone needs pure POSIX, and that's fine when it's flagged as such.Cresida
@Cresida It is not logical to insist on a less compatible solution when a more compatible one is readily available. I am going to look into your suggestion of omitting the * in the pattern, as there might have been a reason for including it.Thumbstall
@Thumbstall Personally, I think that "logical" depends on whether you're prioritising efficiency or applicability. If I'm writing Python then I might use some new 3.10 features because they're helpful or better, even though I'm then reducing the runtimes that it'll work with. If I control the environment then that's a trade-off I can make. If backwards/cross-compatibility is important then great. If not then it can help to be aware of the options.Cresida
@Cresida Once again: I suggested a solution that works with all POSIX-compliant shells. If you want to suggest a solution that only works with $YOUR_FAVORITE_SHELL, then go ahead and post your own answer.Thumbstall
S
13

p7zip, the command line version of 7zip does the job

7z x '*.zip' -o'*'

On Debian and Ubuntu, you have to install p7zip-full.

Sheepshanks answered 17/12, 2014 at 22:54 Comment(1)
simple, and elegant!. awesome! Thanks!Pierian
A
7

Add the folder name and slash after the switch, example:

unzip -d newfolder/ zipfile.zip

zip will create the folder 'newfolder' and extract the archive into it.

Note that the trailing slash is not required but I guess it's just from old habit (I use Debian and Ubuntu).

Albertype answered 18/4, 2018 at 2:12 Comment(1)
For me the slash is required, otherwise the command would complain that there is no such folder.Percy
H
1

aunpack from atool will do this by default for all sorts of archives.

Hydromedusa answered 4/5, 2016 at 9:8 Comment(0)
K
1

In Termux bash I ended up with this, tested for spaces and dots in filenames. It only removes the last file extension and dot for the folder name. This assumes the zip file has a .zip extension otherwise it won't work. Went nuts trying to \ escape the quotes or use single quotes before realizing they just needed to be there as plain quote marks. Drop an echo in front of the zip command, or add a -l to audit the command. For some reason the position of the -d seems important for this implementation.

for f in *.zip; do unzip "$f" \-d "./${f%.*}/"; done; echo -end-
Knotweed answered 26/4, 2021 at 15:15 Comment(0)
F
-1
  1. Open the terminal and locate the 00* files
cat filename.zip.00* > filename.zip

wait until the process ends, it depends on the file size.

  1. The file joining process finished, now you can run the output file
unzip filename.zip
Funky answered 18/5, 2021 at 12:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.