Compare 2 directories and copy differences to directory 3 - linux

I have three directories. I would like to compare directory1 with directory2, then take those changes/new files and copy them over to directory3. Is there an easy way to do this, maybe by using linux diff and cp commands? I'm open to ideas.
Thanks!
Andrew

I believe this is what you want from your description.
for file in dir2/*; do
file_in_dir1=dir1/$(basename ${file})
if [ ! -e ${file_in_dir1} ]; then
# If the file in dir2 does not exist in dir1, copy
cp ${file} dir3
elif ! diff ${file} ${file_in_dir1}; then
# if the file in dir2 is different then the one in dir1, copy
cp ${file} dir3
fi
done
One thing I wasn't sure about is what you wanted if a file exists in dir1 but not dir2.

The thread yonder solves your problem quite nicely, I should think!
Copied from there:
#!/bin/bash
# setup folders for our different stages
DIST=/var/www/localhost/htdocs/dist/
DIST_OLD=/var/www/localhost/htdocs/dist_old/
DIST_UPGRADE=/var/www/localhost/htdocs/dist_upgrade/
cd $DIST
list=`find . -type f`
for a in $list; do
if [ ! -f "$DIST_OLD$a" ]; then
cp --parents $a $DIST_UPGRADE
continue
fi
diff $a $DIST_OLD$a > /dev/null
if [[ "$?" == "1" ]]; then
# File exists but is different so copy changed file
cp --parents $a $DIST_UPGRADE
fi
done

You can also do it without a bash script:
diff -qr ./dir1 ./dir2 | sed -e 's/^Only in\(.*\): \(.*\)/\1\/\2/g' -e 's/ and \..*differ$//g' -e 's/^Files //g' | xargs -I '{}' cp -Rf --parents '{}' ./dir3/
This solution removes all additional text from the diff command using sed, and then copies the files preserving the directory structure.

The two previously posted answers helped me get started but didn't get me all the way there. The solution posted by thomax was really close but I ran into an issue where the cp command on osx doesn't support the --parents parameter so I had to add some logic around the creation of subfolders which made things a bit messy and I had to restructure a bit. Here's what I wound up with:
#!/bin/bash
# setup folders for our different stages
DIST=/var/www/localhost/htdocs/dist/
DIST_OLD=/var/www/localhost/htdocs/dist_old/
DIST_UPGRADE=/var/www/localhost/htdocs/dist_upgrade/
cd $DIST
find . -type f | while read filename
do
newfile=false
modified=false
if [ ! -e "$DIST_OLD$filename" ]; then
newfile=true
echo "ADD $filename"
elif ! cmp $filename $DIST_OLD$filename &>/dev/null; then
modified=true
echo "MOD $filename"
fi
if $newfile || $modified; then
#massage the filepath to not include leading ./
filepath=$DIST_UPGRADE$(echo $filename | cut -c3-)
#create folder for it if it doesnt exist
destfolder=$(echo $filepath | sed -e 's/\/[^\/]*$/\//')
mkdir -p $destfolder
#copy new/modified file to the upgrade folder
cp $filename $filepath
fi
done

Consider you have dir1, dir2 and dir3 on the same level
with the content setup as below:
mkdir dir1
mkdir dir2
echo 1 > dir1/a
echo 1 > dir2/a
echo 2 > dir1/b
echo 3 > dir2/b
echo 4 > dir2/c
cp -r dir1 dir3
When you create and apply patch like this:
diff -ruN dir1 dir2 | patch -p1 -d dir3
Then you have content of dir2 and dir3 equivalent.
If your dir2 is not at the same level as dir1
then you have to edit filenames in the patch
so that you have equal amount of path components
in both dir1 and dir2 filenames.
You should better put your dir2 to the same level as dir1,
because there is no elegant way to do this (at least known to me).
Here follow an "ugly" way.
Consider your dir2 is located in some $BASEDIR
then you should update your diff to trim of the $BASEDIR from dir2's path
like this
diff -ruN dir1 $BASEDIR/dir2 | \
perl -slne 'BEGIN {$base =~ s/\//\\\//g; print $base}
s/\+\+\+ $base\//\+\+\+ /g; print' \
-- -base=$BASEDIR
And then you could apply the resulting path as above.

Related

Script to rename files in subfolders to different name

I have a directory with several folders inside. Inside the folders I have a worksheet with the same name in all folders. I need to run a script that randomly changes the name of the worksheets so that I can throw them all in the same folder. For example: worksheet1
worksheet2
worksheet3.
today they are all called spreadsheet.csv
I have a sketch in linux.
need help please.
NN=0;
for arq in $(ls -1 *.csv);do
let NN++;
rename -n 's/'${arq}'/spreadsheet'${NN}'.csv/' ${arq};
done
(this search all files csv, but dont is recursive. Work only in one directory)
Use globstar:
n=0
shopt -s globstar nullglob || exit
for f in **/*.csv; do
until dst=spreadsheet$(( ++n )).csv; [[ ! -e ${dst} ]]; do
continue
done
mv -i -- "${f}" "${dst}"
done
Sample files:
$ find . -name "*.csv"
./spreadsheet.csv
./sub1/spreadsheet.csv
./sub1/sub2/spreadsheet.csv
One idea:
n=0
while read -r oldname
do
((++n))
newname="${oldname##*/}"
newname="${newname//.csv/-$n.csv}"
echo mv "$oldname" "$newname"
done < <(find . -name spreadsheet.csv)
This generates:
mv ./spreadsheet.csv spreadsheet-1.csv
mv ./sub1/spreadsheet.csv spreadsheet-2.csv
mv ./sub1/sub2/spreadsheet.csv spreadsheet-3.csv
Once OP is satisifed with the output, remove the echo and run the script again.
After removing the echo and running again:
$ find . -name "*.csv"
./spreadsheet-1.csv
./spreadsheet-2.csv
./spreadsheet-3.csv

Renaming files in subdirectories deletes the files

I am writing a script that renames *.MP4 files on an inserted SD card and then rsyncs them.
The directory with the *.MP4 files does not always have the same name:
eg: it could be /DCIM/123_PANA/ or /DCIM/141_PANA/ etc
So I'm trying to write a script that will see what folders are in the /DCIM path, and rename all the *.MP4 files, (there is also a MISC folder in this path which I suspect is causing the issue)
I am using a couple of variables to rename the files also
What I have is:
for f in /media/pi/LUMIX/DCIM/*; do
if [ -d "$f" ]; then
echo $f
for file in $(find $f -name 'P*.MP4')
do
echo $file ">" $(dirname "${file}")/$(date +"%d")$cardname$(basename $file)
mv $file $(dirname "${file}")/$(date +"%d")$cardname$(basename $file)
done
fi
done
But what seems to happen is I end up with a single file with the prefix only (say 08_nb1_) in the _PANA folder, all the others have been deleted. Obviously this is not my desired result!
UPDATE:
$cardname is of the format _nb2_
When I do as asked replace mv with echo here is the output:
/media/pi/LUMIX/DCIM/141_PANA
mv /media/pi/LUMIX/DCIM/141_PANA/P1410192.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410192.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410193.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410193.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410194.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410194.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410195.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410195.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410196.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410196.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410197.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410197.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410198.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410198.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410199.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410199.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410200.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410200.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410201.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410201.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410202.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410202.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410203.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410203.MP4
mv /media/pi/LUMIX/DCIM/141_PANA/P1410204.MP4 /media/pi/LUMIX/DCIM/141_PANA/09_nb2_P1410204.MP4
/media/pi/LUMIX/DCIM/MISC
OK I fixed it by filtering the additional directory names and limiting to only the ones with *_PANA - which solved the issue. I also added a the rsync part and demounted the SD card (if required using Zenity)
A text file placed on the SD card identifies it as a unique card giving each file a unique name when rsyncing it to the backup folder. Renaming on the SD card means
that it can still be used an written to if not full, but we then know which files have been backed up.
Very useful in the field when filming with multiple cards, crews. All running on a Rpi4
for f in /media/pi/LUMIX/DCIM/*_PANA/; do
if [ -d "$f" ]; then
echo "$f"
for file in $(find $f -wholename '*_PANA/P*.MP4')
do
mv "$file" $(dirname "${file}")/$(date +"%d")"$cardname"$(basename "${file}")
done
rsync --stats -u --progress "$f"/*.MP4 /media/pi/VDRIVE/ | tee /home/pi/Documents/ytu/rsync.txt | zenity --icon-name="dialog-warning" \
--width=300 --progress --pulsate --auto-close --auto-kill \
--title="Copying $sdn"
zenity --question --text="Unmount Card?"
if [ $? = 0 ]; then
umount /media/pi/LUMIX
else
exit
fi
fi
done

Difference between two directories ignoring file type?

Is there a quick way to compare two directories but ignore the file extension??
I know the command is typically:
diff dir1 dir2
But in this case apple.gif in dir1 and apple.png in dir2 two are differences.
Is there a way to get apple.gif and apple.png to be considered the same?
You can use bash for loop to copy both dirs to /tmp/ without files names extensions then run diff on the copied dirs and finally delete the temporary dirs such as:
Assuming you have dir/1 and dir/2 you want to compare 1 and 2.
#create all dirs and subdirs
cd dir;
for i in `find . -type d `;do mkdir /tmp/$i ;done
#copy all files without extension(remove string after last '.')
for i in `find . -type f `;do cp $i /tmp/`echo $i | rev | cut -d'.' -f 2- | rev` ;done
#run diff
diff /tmp/1 /tmp/2
#clean up ,remove created dirs
rm -rf /tmp/1 /tmp/2

copy a directory structure with file names without content

I have a huge directory structure of movie files. For analysis of that structure I want to copy the entire directory structure, i.e. folders and files however I don't want to copy all the movie files while I want to keep there file names. Ideally I get zero-byte files with the original movie file name.
I tried to and then rsync to my remote machine which didn't fetch the link files.
Any ideas how to do that w/o writing scripts?
You can use find:
find src/ -type d -exec mkdir -p dest/{} \; \
-o -type f -exec touch dest/{} \;
Find directory (-d) under (src/) and create (mkdir -p) them under dest/ or (-o) find files (-f) and touch them under dest/.
This will result in:
dest/src/<file-structre>
You can user mv creatively to resolve this issue.
Other (partial) solution can be achieved with rsync:
rsync -a --filter="-! */" sorce_dir/ target_dir/
The trick here is the --filter=RULE option that excludes (-) everything that is not (!) a directory (*/)
On ubuntu you can try:
cp -r --attributes-only <source_dir> <target_dir>
It doesn't copy file data.
From manpage of cp
--attributes-only
don't copy the file data, just the attributes
Note: I'm not sure this option available for other distributions, if anybody can confirm please update the answer.
I needed an alternative to this to sync only the file structure:
rsync --recursive --times --delete --omit-dir-times --itemize-changes "$src_path/" "$dst_path"
This is how I realized it:
# sync source to destination
while IFS= read -r -d '' src_file; do
dst_file="$dst_path${src_file/$src_path/}"
# new files
if [[ ! -e "$dst_file" ]]; then
if [[ -d "$src_file" ]]; then
mkdir -p "$dst_file"
elif [[ -f $src_file ]]; then
touch -r "$src_file" "$dst_file"
else
echo "Error: $src_file is not a dir or file"
fi
echo -n "+ "
ls -ld "$src_file"
# modification time changed (files only)
elif [[ -f $dst_file ]] && [[ $(date -r "$src_file") != $(date -r "$dst_file") ]]; then
touch -r "$src_file" "$dst_file"
echo -n "+ "
ls -ld "$src_file"
fi
done < <(find "$src_path" -print0)
# delete files in destination if they disappeared in source
while IFS= read -r -d '' dst_file; do
src_file="$src_path${dst_file/$dst_path/}"
# file disappeard on source
if [[ ! -e "$src_file" ]]; then
delinfo=$(ls -ld "$dst_file")
if [[ -d "$dst_file" ]] && rmdir "$dst_file" 2>/dev/null; then
echo -n "- $delinfo"
elif [[ -f $dst_file ]] && rm "$dst_file"; then
echo -n "- $delinfo"
fi
fi
done < <(find "$dst_path" -print0)
As you can see I use echo and ls to display changes.
ls > listOfMovie.txt; You will have the list of your films in a .txt file
.For multiple directories see the man page.

Shell Script for renaming and relocating the files

I am working on something and need to solve the following. I am giving a analogous version of mine problem.
Say we have a music directory, in which there are 200 directories corresponding to different movies. In each movie directory there are some music files.
Now, say a file music.mp3 is in folder movie.mp3 . I want to make a shell script such that it renames the file to movie_music.mp3 and put it in some folder that I mention to it. Basically, all the files in the subdirectories are to be renamed and to be put in a new directory.
Any workaround for this?
This script receives two arguments: the source folder and the destination folder. It will move every file under any directory under the source directory to the new directory with the new filename:
#!/bin.sh
echo "Moving from $1 to $2"
for dir in "$1"/*; do
if [ -d "$dir" ]; then
for file in "$dir"/*; do
if [ -f "$file" ]; then
echo "${file} -> $2/`basename "$dir"`_`basename "${file}"`"
mv "${file}" "$2"/`basename "$dir"`_`basename "${file}"`
fi
done
fi
done
Here is a sample:
bash move.sh dir dir2
Moving from dir to dir2
dir/d1/f1 -> dir2/d1_f1
dir/d1/f2 -> dir2/d1_f2
dir/d2/f1 -> dir2/d2_f1
dir/d2/f2 -> dir2/d2_f2
Bash:
newdir=path/to/new_directory;
find . -type d |while read d; do
find "$d" -type f -maxdepth 1 |while read f; do
movie="$(basename "$d" |sed 's/\(\..*\)\?//')"
mv "$f" "$newdir/$movie_$(basename $f)";
done;
done
Assuming the following directory tree:
./movie1:
movie1.mp3
./movie2:
movie2.mp3
The following one-liner will create 'mv' commands you can use:
find ./ | grep "movie.*/" | awk '{print "mv "$1" "$1}' | sed 's/\(.*\)\//\1_/'
EDIT:
If your directory structure contains only the relevant directories, you can expand use the following grep instead:
grep "\/.*\/.*"
Notice it looks file anything with at least one directory and one file. If you have multiple inner directories, it won't be good enough.

Resources