Option 1: go back in time and read this: http://lincolnloop.com/blog/2012/jan/6/detecting-file-moves-renames-rsync/
This uses a trick with hardlinks to optimise the operation (note carefully the omission of trailing "/" in the example though, that might be different to what you are used to). If you have a local backup copy prior to the reorg, you can perhaps restore and use that (the complication is being able to create the hardlink copy as required).
Option 2: if you have no spaces/quotes in your file or directory names, and do not intend keeping any duplicates, you can create a quick and dirty copy/rename script like this.
On source:
cd /wherever
find . -type f | xargs sha1sum | sort > /tmp/src.out
On destination
cd /wherever
find . -type f | xargs sha1sum | sort > /tmp/dst.out
Copy the dst.out
file over to source, and then on source do:
join -j 1 /tmp/src.out /tmp/dst.out | while read sum src dst; do
if [ "$src" != "$dst" ]; then
echo mkdir -p $(dirname "$src")
echo cp -ipl "$dst" "$src"
fi
done > fixup.sh
This will output a set of mkdir/cp
commands that you can run (fixup.sh
) in the top directory of your copy on the destination. Make sure that the script output will do what you require it to do. cp -ipl
will not overwrite without prompting, and will copy by hard-linking. A subsequent rsync --delete ...
will delete the old files, assuming that want an identical copy. Use rsync --dry-run ...
afterwards to confirm the extent of any remaining differences.
(It is possible to use "mv -i
" instead of the non-destructive "cp -ipl
", saving the duplication and cleanup.)
If you have problematic file/directory names, you'll need to do some intermediate processing of filenames, or try one of the solutions here: https://unix.stackexchange.com/questions/6411/any-way-to-sync-directory-structure-when-the-files-are-already-on-both-sides
Update:
If you can tolerate the explosion-in-a-punctuation-factory that passes for a sed
command line:
find . -type f -print0 | xargs -0 sha1sum |
sed -re $'s/(^[0-9a-z]*) /\\1__/;
s/ /\\\\x20/g; s/\'/\\\\\'/g;
s/(^[0-9a-z]*)__/\\1 /;
s/ (.*)$/ $\'\\1\'/g;'
This will handle spaces and single/double quotes (though we're approaching perl
territory for proper heavy lifting). It uses the bash $''
construction for quoting troublesome strings.