Compared to other solutions (eg. using the "find" tool), I would like to get a solution that uses the common:
for z in *zip
do
unzip $z
done
however, my parent zip file often has 2, 3, 4 or 5 levels of zipped files. So, for a 2-levels parent zip file I can do:
for z in *zip
do
unzip $z
for x in *zip
do
unzip $x
done
done
which is a pretty basic (or archaic) solution. I was wondering if an if-else call could be included inside it (e.g., [[ -a *zip ]]
) to automagically get all zip files without looping again on the ones that have been already unzipped. The tricky part is that the parent zip file must remain in the current directory (I could use cd
or mkdir
to move it but it may exist a cleaner way).
Any hints are much welcomed,
CodePudding user response:
If you allow to extract the files in hierarchy structure according to the zip files, would you please try:
#!/bin/bash
#
# return nonexistent dirname such as "name(1)"
#
newname() {
local name=$1 # dirname which already exists
local new # new dirname
local i=1 # number for the suffix
while :; do
new=$(printf "%s(%d)" "$name" "$i")
[[ ! -e $new ]] && break # break if the newname is unique
(( i )) # increment for the next loop
done
echo "$new" # return the new name
}
#
# extract zip files in the directory which name is the basename of the zip file
#
extract() {
local zip # zip filename
for zip in "$@"; do
if [[ -f $zip ]]; then # check the existence of the file
local dir=${zip%.zip} # generate the dirname removing extension
if [[ -e $dir ]]; then # if the dirname collides
dir=$(newname "$dir") # then give a new name
fi
unzip "$zip" -d "$dir"
extract "$dir"/*.zip # do it recursively
fi
done
}
extract *.zip
For instance, if you have following files:
- level1.zip contains file1, file2, file3 and level2.zip.
- level2.zip contains file4 and file5.
then the extracted result will have a structure like:
.
|-- level1
| |-- file1
| |-- file2
| |-- file3
| |-- level2
| | |-- file4
| | -- file5
| -- level2.zip
-- level1.zip
[Explanations]
- The
extract
function unzippes the zip files recursively creating new directories with the basename of the zip files. - The
newname
function is used to avoid filename collisions if the directory name to create already exists.
CodePudding user response:
I am wondering why you think find
is not a for
loop, while it loops over directories faster.
task
tree
.
├── a
│ ├── b
│ │ ├── c
│ │ │ ├── d
│ │ │ │ └── hello.zip
│ │ │ └── hello.zip
│ │ └── hello.zip
│ └── hello.zip
└── hello.zip
4 directories, 5 files
find them
mapfile -t zips < <(find -name \*.zip)
unzip them
for zip in "${zips[@]}"; do unzip "$zip" -d "${zip%/*}"; done
done
tree
.
├── a
│ ├── b
│ │ ├── c
│ │ │ ├── d
│ │ │ │ ├── hello.txt
│ │ │ │ └── hello.zip
│ │ │ ├── hello.txt
│ │ │ └── hello.zip
│ │ ├── hello.txt
│ │ └── hello.zip
│ ├── hello.txt
│ └── hello.zip
├── hello.txt
└── hello.zip
4 directories, 10 files