Home > Back-end >  For loop with if-else statement to unzip files
For loop with if-else statement to unzip files

Time:07-20

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

enter image description here

  • Related