-1

I have several compressed files that have embedded/nested compressed files within sub-directories using various compression formats, e.g. 7z, zip, tar. If I use the following approach (see below) to recursively uncompress them, all files are uncompressed to root directory from which I initiated the command. Since none of these compressed files are under my control (I didn't create them), they did not preserve the original directory structure when they archived them. So I'm forced to find a why to do a 'cd' change directory to the sub-directory that they reside under before uncompressing them.

Here is how I'm going about it now...

$ find . -type f -name "*.zip" -exec 7z x -r -spe -tzip '{}' \;
$ find . -type f -name "*.7z" -exec 7z x -r -spe '{}' \;

How do I go about doing a 'cd' given where the compressed file was found and uncompressing at the location? I image that some sort of 'while' loop will be needed with addition checks and commands.

Prasanna
  • 2,390
  • 10
  • 11
bbboomer
  • 27
  • 1
  • 1
  • 6

1 Answers1

1
find . -type f -name "*.zip" -print0 | xargs -0 -I @ bash -c 'cd $(dirname "@"); 7z x -r -spe -tzip $(basename "@")'
find . -type f -name "*.7z" -print0 | xargs -0 -I @ bash -c 'cd $(dirname "@"); 7z x -r -spe $(basename "@")'

This uses the dirname command to extract the directory of a file and the basename command to extract the filename itself.

To make sure this is secure and works with filenames with spaces in them, we print null-terminated strings (-print0 coupled with xargs -0) instead of using -exec. xargs will take all lines from stdin and invoke the given command line, which here calls bash to do a cd to the directory followed by the unarchive command on the filename itself.

If you don't have the dirname/basename utilities installed, and it appears you do not, you can also use this (note: this does not work for find /):

find . -type f -name "*.zip" -print0 | xargs -0 -I @ bash -c 'fname="@"; base="${fname##*/}"; dir="${fname%/${base}}"; cd "${dir}"; 7z x -r -spe -tzip "${base}"'
find . -type f -name "*.7z" -print0 | xargs -0 -I @ bash -c 'fname="@"; base="${fname##*/}"; dir="${fname%/${base}}"; cd "${dir}"; 7z x -r -spe "${base}"'

This is a little more complicated. First we need to assign the value xargs reads (@, specified by the xargs -I option) to a variable for manipulation. Note: This will not work if the filename contains quotes or escape sequences, which I trust it will not. The ${fname##*/} will delete everything up to and including a final slash on the left-hand side, thus removing the directory and forming a "basename". This gets assigned to $base. The directory is everything that is not the base, so we remove the base from the original filename (${fname%/${base}}) and call that $dir. We change directory to the directory of interest and unarchive the basename.

[eje@localhost recurse-cd]$ ls -R
.:
a  b

./a:
a  a.zip  b  c

./a/a:
a-a.zip

./a/b:
a-b.zip

./a/c:
a-c.zip

./b:
a  b  b.zip

./b/a:
b-a.zip

./b/b:
b-b.zip

[eje@localhost recurse-cd]$ find . -type f -name "*.zip" -print0 | xargs -0 -I @ bash -c 'cd $(dirname @); unzip $(basename @)' 
Archive:  a-a.zip
 extracting: a-a                     
Archive:  a-b.zip
 extracting: a-b                     
Archive:  a-c.zip
 extracting: a-c                     
Archive:  a.zip
 extracting: a-file                  
Archive:  b-a.zip
 extracting: b-a                     
Archive:  b-b.zip
 extracting: b-b                     
Archive:  b.zip
 extracting: b-file                  
[eje@localhost recurse-cd]$ ls -R
.:
a  b

./a:
a  a-file  a.zip  b  c

./a/a:
a-a  a-a.zip

./a/b:
a-b  a-b.zip

./a/c:
a-c  a-c.zip

./b:
a  b  b-file  b.zip

./b/a:
b-a  b-a.zip

./b/b:
b-b  b-b.zip
Vercingatorix
  • 1,838
  • 1
  • 13
  • 22
  • when attempting to run your recommended script I get... ' Line 0: cd: too many arguments' 'Scanning the Drive for archives:' 'System ERROR:' 'unknown error -2147024872' – bbboomer Dec 21 '20 at 18:40
  • 1) This is Linux, as you've tagged it, correct? 2) From the command line, type `dirname ./hello`. It should print ".". If it doesn't, you have another program with the same name installed. There may be a solution in that case, but let's find out what you have first. – Vercingatorix Dec 21 '20 at 19:21
  • I added a pure `bash` version; see if that works for you. – Vercingatorix Dec 21 '20 at 20:02
  • It return "." as you indicate. If I try your modified version, I get same results. Also, I'm working in a disconnected environment so I can't copy and paste or move anything to this machine. So I'm fat fingering in your script. I've gone over it many times to ensure correct syntax that you specified. – bbboomer Dec 21 '20 at 20:43
  • I get same error but after each iteration I do see .zip files being referenced. It's as thou it is trying to unzip files but perhaps looking in wrong directories. I can' t be sure since it doesn't output any info about what directory it's in when it attempts to unzip – bbboomer Dec 21 '20 at 20:49
  • After complete retyping it in. It seems to be working. Let me play with it some more and validity check the many nested and subdirectories and embededed compressed files to ensure that everything is uncompressing. – bbboomer Dec 21 '20 at 21:18
  • Thank you for the time and effort put into this resolution. I wish I completely understood how scripts like this worked, but for now I'm settling for the answer. Regards.. – bbboomer Dec 22 '20 at 13:25