Find and copy, rename files with same name - bash

I have this find
find "$source_folder" -name "IMG_[0-9][0-9][0-9][0-9].JPG" -exec cp {} $destination_folder \;
i want only the IMG_[0-9][0-9][0-9][0-9].JPG, in the source folder there are diferent files with same name,and same files with same name, how can i copy everything and rename all the same name files with extra .JPG without deleting any unique files?
PS: noob, please could you explain so i can try and learn

I'll assume that your file names don't contain any whitespace. It makes things easier.
You can pipe the output of the find command to a loop where you can run some tests whether or not you want to copy the file.
I have to determine the name of the file and where it is copied to. In order to do that, I have to strip off the $source_folder from the name of the file I find, and prepend the name of the $dest_folder. This is where I want to actually copy the file.
Your directions are a bit confusing. I am assuming you're doing the copy if the $dest_file doesn't exist or it is different from the source. Once I determine that this is the file you want me to copy, I have to make sure the destination directory exists, and if it doesn't I create it. Now, I can do my copy.
I have two echo statements in here. This way, you can do a dry run of this script to make sure it's doing what you want it to do. If it looks good, you can remove the echo commands from the two lines and rerun the script.
find "$source_folder" -name "IMG_[0-9][0-9][0-9][0-9].JPG" | while read file_name
do
rootname=${file_name#$source_folder/} # Removes the source folder from the name
dest_name="${dest_folder}/$rootname"
if [ ! -e "$dest_name" ] || [ ! diff "$file_name" "$dest_name" > /dev/null 2>&1 ]
then
$dest_folder=$(basename $dest_name)
[ ! -d "$dest_folder" ] && echo mkdir -p "$dest_folder" #Remove echo if it works
echo cp "$file_name" "$dest_name" #Remove 'echo' if it works
fi
done

Related

Bash scanning for filenames containing keywords and move them

I'm looking to find a way to constantly scan a folder tree for new subfolders containing MKV/MP4 files. If that file contains a keyword and ends in MP4 or MKV, it'll be moved to a defined location matching that keyword. As a bonus, it would delete the folder and all it's leftover contents where the file resided previosly. The idea would be to have this run in the background and sort everything where it belongs and clean up after itself if possible.
example:
Media\anime\Timmy\Timmy_S1E1\Timmy_S1E1_720p.mkv #Found Keyword Timmy, allowed filetype
Move to destination:
Media\series\Timmy\
Delete subfolder:
Media\anime\Timmy\Timmy_S1E1\
I would either do separate scripts for each keyword, or, if possible, have the script match each keyword with a destination
#!/bin/bash
#!/bin/sh
#!/etc/shells/bin/bash
while true
do
shopt -s globstar
start_dir="//srv/MEDIA2/shows"
for name in "$start_dir"/**/*.*; do
# search the directory recursively
done
sleep 300
done
This could be done by:
creating a script that does what you want to do, once.
run the script from cron, at a certain interval. Say a couple minutes, or a couple hours, depends on the volume of files you receive.
no need for a continually running daemon.
Ex:
#!/bin/bash
start_dir="/start/directory"
if [[ ! -d "$start_dir" ]]
then
echo "ERROR: start_dir ($start_dir) not found."
exit 1
fi
target_dir="/target/directory"
if [[ ! -d "$target_dir" ]]
then
echo "ERROR: target_dir ($target_dir) not found."
exit 1
fi
# Move all MP4 and MKV files to the target directory
find "$start_dir" -type f \( -name "*keyword*.MP4" -o -name "*keyword*.MKV" \) -print0 | while read -r -d $'\0' file
do
# add any processing here...
filename=$(basename "$file")
echo "Moving $filename to $target_dir..."
mv "$file" "$target_dir/$filename"
done
# That being done, all that is left in start_dir can be deleted
find "$start_dir" -type d ! -path "$start_dir" -exec /bin/rm -fr {} \;
Details:
scanning for files is most efficient with the find command
the -print0 with read ... method is to ensure all valid filenames are processed, even if they include spaces or other "weird" characters.
the result of the above code is that each file that matches your keyword, with extensions MP4 or MKV will be processed once.
you can then use "$file" to access the file being processed in the current loop.
make sure you ALWAYS double quote $file, otherwise any weird filename will brake your code. Well you should always double quote your variables anyway.
more complex logic can be added for your specific needs. Ex. create the target directory if it does not exist. Create a different target directory depending on your keyword. etc.
to delete all sub-directories under $start_dir, I use find. Again this will process weird directory names.
One point, some will argue that it could all be done in 1 find command with -exec option. True, but IMHO the version with the while loop is easier to code, understand, debug, learn.
And this construct is good to have in your bash toolbox.
When you create a script, only one #! line is needed.
And I fixed the indentation in your question, much easier to read your code properly indented and formatted (see the edit help in the question editor).
Last point to discuss, lets say you have a LARGE number of directories and files to process, and it is possible that new files are added while the script is running. Ex. you are moving many MP4 files, and while it is doing it, new files are deposited in the directories. Then when you do the deletion you could potentially loose files.
If such a case is possible, you could add a check for new files just before you do the /bin/rm, it would help. To be absolutely certain, you could setup a script that processes 1 file, and have it triggered by inotify. But that is another ball game, more complicated and out of scope for this answer.

How can I copy files recursively without overwriting duplicate file names?

Let's say I have files nested in subdirectories with potentially duplicate file names. I want to copy all files to a new directory but prevent overwriting AND preserve filenames (mostly).
The following does not work because it overwrites duplicate filenames:
find /SourceDir/. -type f -exec cp -pv \{\} /DestDir/ \;
Adding noclobber (cp -n) doesn't help either because duplicates are just skipped.
Current File Structure:
SourceDir
--SubdirA
----File1.gif
---- ...
----File1000.jpg
--SubdirB
----File1.gif
---- ...
----File1000.png
...
--SubdirZ
----SubdirAA
------File1.sh
------ ...
------File1000.jpg
Desired File Structure:
DestDir
--File1.gif
--File1_1.gif <-- original name was `File1.gif` but this already existed
--File2.jpg
--File2.gif <-- `File2.jpg` already exists, but not `File2.gif`
--File3.gif
--File3_1.gif
--File4.jpg
--File4_1.jpg
--File4_2.jpg <-- original name was `File4.jpg`, but `File4_1.jpg` already existed too.
-- ...
--File1000.png
I do not want to rename every file. And I don't want to give arbitrary hashes to those I need to duplicate. What do you recommend?
I'm on a Mac, so Linux commands are all fair game.
Here is one solution.
#!/bin/bash
SourceDir=".";
DestDir="../dest";
cd ${SourceDir}
find . -type f |
while read x
do
bn=`basename $x`;
if [ -f "${DestDir}/$bn" ]
then
for i in {1..9999}
do
if [ ! -f "${DestDir}/${bn%.*}_${i}.${bn##*.}" ]
then
echo "Next free file extension is no $i";
bn="${DestDir}/${bn%.*}_${i}.${bn##*.}"
break;
fi
done
fi
echo "copy file $x to ${DestDir}/$bn";
cp -p "$x" "${DestDir}/$bn";
done
Please let me know if that works for you.

Move files to the correct folder in Bash

I have a few files with the format ReportsBackup-20140309-04-00 and I would like to send the files with same pattern to the files as the example to the 201403 file.
I can already create the files based on the filename; I would just like to move the files based on the name to their correct folder.
I use this to create the directories
old="directory where are the files" &&
year_month=`ls ${old} | cut -c 15-20`&&
for i in ${year_month}; do
if [ ! -d ${old}/$i ]
then
mkdir ${old}/$i
fi
done
you can use find
find /path/to/files -name "*201403*" -exec mv {} /path/to/destination/ \;
Here’s how I’d do it. It’s a little verbose, but hopefully it’s clear what the program is doing:
#!/bin/bash
SRCDIR=~/tmp
DSTDIR=~/backups
for bkfile in $SRCDIR/ReportsBackup*; do
# Get just the filename, and read the year/month variable
filename=$(basename $bkfile)
yearmonth=${filename:14:6}
# Create the folder for storing this year/month combination. The '-p' flag
# means that:
# 1) We create $DSTDIR if it doesn't already exist (this flag actually
# creates all intermediate directories).
# 2) If the folder already exists, continue silently.
mkdir -p $DSTDIR/$yearmonth
# Then we move the report backup to the directory. The '.' at the end of the
# mv command means that we keep the original filename
mv $bkfile $DSTDIR/$yearmonth/.
done
A few changes I’ve made to your original script:
I’m not trying to parse the output of ls. This is generally not a good idea. Parsing ls will make it difficult to get the individual files, which you need for copying them to their new directory.
I’ve simplified your if ... mkdir line: the -p flag is useful for “create this folder if it doesn’t exist, or carry on”.
I’ve slightly changed the slicing command which gets the year/month string from the filename.

Rename files within folders to folder names while retaining extensions

I have a large repository of media files that follow torrent naming conventions- something unpleasant to read. At one point, I had properly named the folders that contain said files, but not want to dump all the .avi, .mkv, etc files into my main media directory using a bash script.
Overview:
Current directory tree:
Proper Movie Title/
->Proper.Movie.Title.2013.avi
->Proper.Movie.Title.2013.srt
Title 2/
->Title2[proper].mkv
Movie- Epilogue/
->MOVIE EPILOGUE .AVI
Media Movie/
->MEDIAMOVIE.CD1.mkv
->MEDIAMOVIE.CD2.mkv
.
.
.
Desired directory tree:
Proper Movie Title/
->Proper Movie Title.avi
->Proper Movie Title.srt
Title 2.mkv
Movie- Epilogue.avi
Media Movie/
->Media Movie.cd1.mkv
->Media Movie.cd2.mkv
Though this would be an ideal, my main wish is for the directories with only a single movie file within to have that file be renamed and moved into the parent directory.
My current approach is to use a double for loop in a .sh file, but I'm currently having a hard time keeping new bash knowledge in my head.
Help would be appreciated.
My current code (Just to get access to the internal movie files):
#!/bin/bash
FILES=./*
for f in $FILES
do
if [[ -d $f ]]; then
INFILES=$f/*
for file in $INFILES
do
echo "Processing >$file< folder..."
done
#cat $f
fi
done
Here's something simple:
find * -type f -maxdepth 1 | while read file
do
dirname="$(dirname "$file")"
new_name="${dirname##*/}"
file_ext=${file##*.}
if [ -n "$file_ext" -a -n "$dirname" -a -n "$new_name" ]
then
echo "mv '$file' '$dirname/$new_name.$file_ext'"
fi
done
The find * says to run find on all items in the current directory. The -type f says you only are interested in files, and -maxdepth 1 limits the depth of the search to the immediate directory.
The ${file##*.} is using a pattern match. The ## says the largest left hand match to *. which is basically pulling everything off to the file extension.
The file_dir="$(dirname "$file")" gets the directory name.
Note quotes everywhere! You have to be careful about white spaces.
By the way, I echo instead of doing the actual move. I can pipe the output to a file, examine that file and make sure everything looks okay, then run that file as a shell script.

Get directories without specific filetype cygwin/bash

I am trying to organise my music collection's album art, for XBMC etc. and my own personal OCD based reasons. To do this, I want to loop through each folder in my music collection, (there are also files in music hence $(find . -type d). I check each folder for the existence of a jpeg. If it exists, it is copied to be called cover.jpg. If no jpeg is found, the name of the folder should be added to a list, which I'll then search google for myself. The code I'm using is below.
#!/bin/bash
IFS=$'\n'
touch missing
cd /cygdrive/d/music
for i in $(find . -type d)
do cd $i
if [ -e '*.jpg' ]
then
for j in *.jpg
do cp $j 'cover.jpg'
done
else
cd ..
$i >> missing
fi
done
The Problems
cd $i always fails, as the directory does not exist.
$i >> missing always fails, with a permission is denied error, however ls -l missing gives -rwxrwxrwx+ 1 Username None 0 Sep 13 16:45 missing
Update:
After trying several iterations to get the script to work, I had to give up and simply use this Album Art Downloader, which in hindsight was the obvious thing to do. I'm marking the answer with a revised script as accepted as it got me closer to the solution I know is out there, somewhere.
1) cd $i always fails, as the directory does not exist.
since the result of find . -type -d is relative to your where you initial working directory, you'll need to cd back to the base directory before doing the nextcd $1. A quick way to return to the previous directory is cd -.
2) $i >> missing always fails, with a permission is denied error,
Two problems here. First of all, by doing $i >> missing you're actually trying to execute $i as a command. If you want the filename appended to the file, you'll need to use echo $i >> missing.
Secondly, you created missing in your initial working directory, and so you're actually dealing with a different missing file once you've cd'ed somewhere else. To refer to the same file, use an absolute path.
Try this: (I tried to keep the script as close to what you have as possible and commented on lines that I changed. I've also quoted all paths to better handle filenames with spaces)
#!/bin/bash
IFS=$'\n'
MISSING="$(pwd)/missing" # store absolute path
> "$MISSING". # create new or empty existing
cd /cygdrive/d/music
for i in $(find . -type d)
do
cd "$i"
if [ -e *.jpg ] # wildcard not expanded if quoted
then
for j in *.jpg
do cp "$j" 'cover.jpg'
done
else
echo "$i" >> "$MISSING" # use $MISSING
fi
cd - # go back to previous directory
done
p.s. Just in case you haven't noticed, do be aware that if you have more than one *.jpg file in a directory, your script will essentially repeatedly overwrite cover.jpg with the last one ending up as the actual version. Also, you're likely to see the error "cp: 'cover.jpg' and 'cover.jpg' are the same file" if you have more than one jpg file and when cover.jpg already exists.
cd $i will fail for a file like "Artist Name - Song Name.mp3". The shell will break it into Artist Name etc. on whitespace and it will break. You could print $i before doing the cd to debug the problem.
$i >> missing will fail because you're trying to execute $i as a script and output the contents into missing. If you want to put the file name, you should use something like echo "$i" >> missing.
I recommend you use a real scripting language like Perl or Python rather than do this in shell. Files with spaces in the name are a real pain.

Resources