Find and Unrar all files - bash

Hi I am working on a script that syncs content from a remote site using SFTP, then extracts any archives. However I am struggling to get the extracted files to the correct (source) directory.
I started with a for loop, but was thinking this could be more efficient.
list=`find $local_dir -type f -name *.rar`
for line in $list; do
DEST=${line%/*}
unrar x -o- $line $DEST
done
Requirements:
1. Find all RAR files
2. Extract all rar files to the directory each rar is located
/sync/testfile.rar --> /sync/testfile.file
/sync/testdir/testfile1.rar --> sync/testdir/testfile1.file
/sync/testdir/testfile2.rar --> sync/testdir/testfile2.file
Here is my current command. In the loop I used I specifically called out the destination, but am not sure how to do that here in one line.
find $local_dir -type f -name '*.rar' -exec unrar x -o- {} \;
I have also tried unrar xe -o- but that gives me the same result where the contents are extracted to the directory where the script was ran from.

Maybe:
find $local_dir -type f -name '*.rar' -exec sh -c 'echo unrar x -o- {} $(dirname {})' \;
I prepended unrar with echo so that it's possible to examine what
command is going to do. For example, for the directory structure like
this:
$ tree
.
├── a.rar
├── b
│   └── z.rar
└── b.rar
1 directory, 3 files
It would print:
$ find $local_dir -type f -name '*.rar' -exec sh -c 'echo unrar x -o- "{}" "$(dirname {})"' \;
unrar x -o- ./b.rar .
unrar x -o- ./a.rar .
unrar x -o- ./b/z.rar ./b

Basically in your example "-exec" should be "-execdir" (unrar switches not tested)
Executed from the folder above sync:
folderAboveSync$ find -name '*.rar' -execdir unrar e {} \;
http://man7.org/linux/man-pages/man1/find.1.html

Related

copy files with the base directory

I am searching specific directory and subdirectories for new files, I will like to copy the files. I am using this:
find /home/foo/hint/ -type f -mtime -2 -exec cp '{}' ~/new/ \;
It is copying the files successfully, but some files have same name in different subdirectories of /home/foo/hint/.
I will like to copy the files with its base directory to the ~/new/ directory.
test#serv> find /home/foo/hint/ -type f -mtime -2 -exec ls '{}' \;
/home/foo/hint/do/pass/file.txt
/home/foo/hint/fit/file.txt
test#serv>
~/new/ should look like this after copy:
test#serv> ls -R ~/new/
/home/test/new/pass/:
file.txt
/home/test/new/fit/:
file.txt
test#serv>
platform: Solaris 10.
Since you can't use rsync or fancy GNU options, you need to roll your own using the shell.
The find command lets you run a full shell in your -exec, so you should be good to go with a one-liner to handle the names.
If I understand correctly, you only want the parent directory, not the full tree, copied to the target. The following might do:
#!/usr/bin/env bash
findopts=(
-type f
-mtime -2
-exec bash -c 'd="${0%/*}"; d="${d##*/}"; mkdir -p "$1/$d"; cp -v "$0" "$1/$d/"' {} ./new \;
)
find /home/foo/hint/ "${findopts[#]}"
Results:
$ find ./hint -type f -print
./hint/foo/slurm/file.txt
./hint/foo/file.txt
./hint/bar/file.txt
$ ./doit
./hint/foo/slurm/file.txt -> ./new/slurm/file.txt
./hint/foo/file.txt -> ./new/foo/file.txt
./hint/bar/file.txt -> ./new/bar/file.txt
I've put the options to find into a bash array for easier reading and management. The script for the -exec option is still a little unwieldy, so here's a breakdown of what it does for each file. Bearing in mind that in this format, options are numbered from zero, the {} becomes $0 and the target directory becomes $1...
d="${0%/*}" # Store the source directory in a variable, then
d="${d##*/}" # strip everything up to the last slash, leaving the parent.
mkdir -p "$1/$d" # create the target directory if it doesn't already exist,
cp "$0" "$1/$d/" # then copy the file to it.
I used cp -v for verbose output as shown in "Results" above, but IIRC it's also not supported by Solaris, and can be safely ignored.
The --parents flag should do the trick:
find /home/foo/hint/ -type f -mtime -2 -exec cp --parents '{}' ~/new/ \;
Try testing with rsync -R, for example:
find /your/path -type f -mtime -2 -exec rsync -R '{}' ~/new/ \;
From the rsync man:
-R, --relative
Use relative paths. This means that the full path names specified on the
command line are sent to the server rather than just the last parts of the
filenames.
The problem with the answers by #Mureinik and #nbari might be that the absolute path of new files will spawn in the target directory. In this case you might want to switch to the base directory before the command and go back to your current directory afterwards:
path_current=$PWD; cd /home/foo/hint/; find . -type f -mtime -2 -exec cp --parents '{}' ~/new/ \; ; cd $path_current
or
path_current=$PWD; cd /home/foo/hint/; find . -type f -mtime -2 -exec rsync -R '{}' ~/new/ \; ; cd $path_current
Both ways work for me at a Linux platform. Let’s hope that Solaris 10 knows about rsync’s -R ! ;)
I found a way around it:
cd ~/new/
find /home/foo/hint/ -type f -mtime -2 -exec nawk -v f={} '{n=split(FILENAME, a, "/");j= a[n-1];system("mkdir -p "j"");system("cp "f" "j""); exit}' {} \;

bash script optimization file rename

i am a total noob, but i figured out this script for doing the following:
I have a folder called "unrar" in there are subfolders with unknown foldername with rar file inside.
Now i enter unknownsubfolder, find rar file and unrar it in unknownsubfolder.
After that i find the new file and rename it with the unknownsubfoldername. Now i grab the file and move it to ./unrar.
#!/bin/bash
cd /home/user/unrar/
for dir in /home/user/unrar/*;
do (cd "$dir" && find -name "*.rar" -execdir unrar e -r '{}' \;); done
echo "$(tput setaf 2)-> unrar done!$(tput sgr0)"
for dir in /home/user/unrar/*;
do (cd "$dir" && find -name "*.mkv" -exec mv '{}' "${PWD##*\/}.mkv" \;); done
for dir in /home/user/unrar/*;
do (cd "$dir" && find -name "*.mp4" -exec mv '{}' "${PWD##*\/}.mp4" \;); done
for dir in /home/user/unrar/*;
do (cd "$dir" && find -name "*.avi" -exec mv '{}' "${PWD##*\/}.avi" \;); done
cd /home/user/unrar
find -name "*.mkv" -exec mv '{}' /home/user/unrar \;
find -name "*.mp4" -exec mv '{}' /home/user/unrar \;
find -name "*.avi" -exec mv '{}' /home/user/unrar \;
This works fine with most files, but in some cases it doesn't
I want to find *.rar in DIR and unrar it. the newfile.(.mkv|.avi|.mp4) should be renamed to DIR(.mkv|.avi|.mp4) and moved to ./unrar
This is my filestructure.
./unrar/
- unknownsubfolder/
-file.rar
-file.r00
-....
- unknownsubfolder1/
- s01/
- file.rar
- file.r00
- ....
- s02/
- file.rar
- file.r00
- ....
- ....
If case1, unrar "/unknownsubfolder/file.rar" and get "x.mkv". the file is renamed from "x.mkv" to "unknwonsubfolder.mkv" and moved to "./unrar/unknownsubfolder.mkv"
(same with *.avi + *.mp4) ==perfekt
if case2, in my script unknownsubfolder/s01/file.rar will be unrard, but not renamed to s01.mkv insted to unknwonsubfolder1.mkv.
(if there are more like s02, s03, s04 ...) i always end up with one unknownsubfolder.mkv file in ./unrar) ==wrong output
So i guess i have 3 questions
How do i get the right DIRname for renaming the file? Or how do i enter unknownsubfolder/s01 ....?
Is there a way to exclude a word from the find? sometimes "unknownsubfolder" contains another folder+file called "sample(.mkv|.avi|.mp4)". I would like to exclude that, to prevent the original file to be overwritten with the sample file. happens sometimes.
I am sure i can combine some of the code,to make it even shorter. Could someone explain how? So how i combine the mkv,avi and mp4 in one line.
regards, wombat
(EDIT: for better understanding)
UPDATE:
I adjusted the solution to work with unrar. Since I did not had unrar installed previously, I used gunzip to construct the solution and then simply replaced it with unrar. The problem with this approach was that, by default, unrar extracts to the current working directory. Another difference is that the name of the extracted file can be completely different from the archive's name - it is not just a matter of different extensions. The original archive is also not deleted after extraction.
Here is the solution specifically tailored to work with unrar with respect to aforementioned behavior:
#!/bin/bash
path="$1"
omit="$2"
while read f;do
unrar e -r "${f}" "${f%/*}" > /dev/null
done < <(find "${path}" -type d -name "${omit}" -prune -o -type f -print)
while read f;do
new="${f%/*}"
new="${new##*/}"
mv "${f}" "${path}/${new}"
done < <(find "${path}" -type d -name "${omit}" -prune -o -type f -a \! -name '*.rar' -print )
You can save the script, e.g., as rename-script (do not forget to make it executable), and then call it like
./rename-script /path/to/unrar omitfolder
Notice, that inside the script there is no cd. You will have to at least provide the location of the unrar folder as first parameter, otherwise you will get an error. In case of OP this would be /home/user/unrar. The omitfolder is not a path, it is just the name of the folder that you want to omit. So in OP's case this would be sample.
./rename-script /home/user/unrar sample
As requested by OP in the comments, you can read about the bash read-builtin and process substitution in order to understand how the while-loop works and how it assigns the filenames returned by find to the variable f.

Concatenate folder name into file

If I have a bunch of folders (f1, f2, f3) and they all have images in them (image1.jpg, image2.jpg) - I'd like to add the folder name into the image name itself.
Ie: f1_image1.jpg
What's the best way to do this? Preferably something I can run on Terminal.
Something like this?
for dir in *
do
for image in ${dir}/*.jpg
do
# remove the 'echo' if you think this works for you
echo mv "${image}" "${dir}_$(basename ${image})"
done
done
This worked for me:
for dir in *
do
for image in ${dir}/*.jpg
do
image_name=$(echo ${image} | cut -f2 -d/)
mv "${image}" "${dir}_${image_name}"
done
done
Some one liner based on find
find <path> -type f -exec sh -c 'mv {} $(dirname {})/$(basename $(dirname {}))_$(basename {})' \;
where <path> is the root folder of your bunch of folders
example:
create the example files
mkdir -p /tmp/ffff/f{1,2,3}
touch /tmp/ffff/f{1,2,3}/image{1,2,3,4}.jpg
run
find /tmp/ffff -type f
you will get
/tmp/ffff/f3/image4.jpg
/tmp/ffff/f3/image3.jpg
/tmp/ffff/f3/image2.jpg
/tmp/ffff/f3/image1.jpg
/tmp/ffff/f2/image4.jpg
/tmp/ffff/f2/image3.jpg
/tmp/ffff/f2/image2.jpg
/tmp/ffff/f2/image1.jpg
/tmp/ffff/f1/image4.jpg
/tmp/ffff/f1/image3.jpg
/tmp/ffff/f1/image2.jpg
/tmp/ffff/f1/image1.jpg
run
find /tmp/ffff -type f -exec sh -c 'mv {} $(dirname {})/$(basename $(dirname {}))_$(basename {})' \;
run again
find /tmp/ffff -type f
you will get
/tmp/ffff/f3/f3_image1.jpg
/tmp/ffff/f3/f3_image2.jpg
/tmp/ffff/f3/f3_image3.jpg
/tmp/ffff/f3/f3_image4.jpg
/tmp/ffff/f2/f2_image1.jpg
/tmp/ffff/f2/f2_image2.jpg
/tmp/ffff/f2/f2_image3.jpg
/tmp/ffff/f2/f2_image4.jpg
/tmp/ffff/f1/f1_image1.jpg
/tmp/ffff/f1/f1_image2.jpg
/tmp/ffff/f1/f1_image3.jpg
/tmp/ffff/f1/f1_image4.jpg

Move the files to a specific folder in unix

I have a script to move files of type .txt to a particular folder .It looks for the files in work folder and move it to completed folder.
I would like to make the script generic i.e to enhance the script so that the scripts works not for just one particular folder but other similar folders as well.
Example: If there is a .txt file in folder /tmp/swan/test/work and also in folder /tmp/swan/test11/work, the files should move to /tmp/swan/test/done and /tmp/swan/test11/done respectively.
EDIT:Also, if there is a .txt file in a sub folder like /tmp/swan/test11/work/APX that should also move to /tmp/swan/test11/done
Below is the current script.
#!/bin/bash
MY_DIR=/tmp/swan
cd $MY_DIR
find . -path "*work*" -iname "*.txt" -type f -execdir mv '{}' /tmp/swan/test/done \;
With -execdir, the mv command is executed in whatever directory the file is found in. Since you just want to move the file to a "sibling" directory, each command can use the same relative path ../done.
find . -path "*work*" -iname "*.txt" -type f -execdir mv '{}' ../done \;
One way to do it:
Background:
$ tree
.
├── a
│   └── work
└── b
└── work
Renaming:
find . -type f -name work -exec \
sh -c 'echo mv "$1" "$(dirname "$1")"/done' -- {} \;
Output:
mv ./a/work ./a/done
mv ./b/work ./b/done
You can remove the echo if it does what you want it to.
What about:
find . -path '*work/*.txt' -exec sh -c 'd=$(dirname $(dirname $1))/done; mkdir -p $d; mv $1 $d' _ {} \;
(also creates the target directory if it does not exist already).

Regex find and copy in bash (preserving folder structure)?

I have a folder with a bunch of log files. Each set of log files is in a folder detailing the time and date that the program was run. Inside these log folders, I've got some video files that I want to extract. All I want is the video files, nothing else. I tried using this command to only copy the video files, but it didn't work because a directory didn't exist.
.rmv is the file extension of the files I want.
$ find . -regex ".*\.rmv" -type f -exec cp '{}' /copy/to/here/'{}'
If I have a folder structure such as:
|--root
|
|--folder1
| |
| |--file.rmv
|
|--folder2
|
|--file2.rmv
How can I get it to copy to copy/to/here with it copying the structure of folder1 and folder2 in the destination directory?
cp has argument --parents
so the shortest way to do what you want is:
find root -name '*.rmv' -type f -exec cp --parents "{}" /copy/to/here \;
I would just use rsync.
The {} represents the full path of the found file, so your cp command evaluate to this sort of thing:
cp /root/folder1/file.rmv /copy/to/here/root/folder1/file.rmv
If you just drop the second {} it will instead be
cp /root/folder1/file.rmv /copy/to/here
the copy-file-to-directory form of cp, which should do the trick.
Also, instead of -regex, yor could just use the -name operand:
find root -name '*.rmv' -type f -exec cp {} /copy/to/here \;
Assuming src is your root and dst is your /copy/to/here
#!/bin/sh
find . -name *.rmv | while read f
do
path=$(dirname "$f" | sed -re 's/src(\/)?/dst\1/')
echo "$f -> $path"
mkdir -p "$path"
cp "$f" "$path"
done
putting this in cp.sh and running ./cp.sh from the directory over root
Output:
./src/folder1/file.rmv -> ./dst/folder1
./src/My File.rmv -> ./dst
./src/folder2/file2.rmv -> ./dst/folder2
EDIT: improved script version (thanks for the comment)

Resources