Reputation: 261
I have a directory (dir1
, say) where each sub-directory contains the same contents. Like this:
dir1
sub1
subsub1
file1
file2
subsub2
[etc.]
sub2
[same as in sub1]
sub3
[...]
aberration
So, you see, there's also one sub-directory which is different.
Then, I have another directory (dir2
), which has contents that I would like to use to replace the contents of each of sub1
, sub2
, sub3
, etc., but NOT of aberration
. I would like to overwrite any files and directories in the target (where dir2/subsub1
would replace the entire dir1/sub5/subsub1
directory, regardless of its contents). [Edit: I realized I had dir1 and dir2 reversed in this previous sentence. I have edited it to correct the error.]
Is there a single command I can run to accomplish this? I've looked at cp
with xargs
and rsync
with a for
loop, but I've gotten my brain in a twist.
I'm also happy to write this as a shell script, but I'm still lost.
Upvotes: 0
Views: 787
Reputation: 753695
Superficially:
cd /path/to/dir1
cp -rf sub* /path/to/dir2
As a technique, it relied on being able to distinguish aberration
from sub1
etc via the glob pattern sub*
. Adapt that to suit the details of your environment. If the worst comes to the worst, create a file with the list of non-aberrational directory names:
cp -rf $(<non-aberrational-names) /path/to/dir2
This won't remove files found under dir2/subN that are not in dir1/subN. For that, you'd probably add:
cd /path/to/dir2 &&
rm -fr sub*
cd /path/to/dir1
cp -fr sub* /path/to/dir2
Be cautious - the rm -fr
is dangerous.
In a comment, I noted:
If you value your sanity, use consistent names. If you're hobbled by other people's insanity, then you'll have to specify a mapping between the directories under
/path/to/dir1
and/path/to/dir2
, probably by a file (and let's hope for your sake that the pathnames don't contain spaces!). You can then do:while read src dst; do cp -fr $src $dst; done < directory-mapping-file
.
To which the response was:
Ah yes, my sanity. Already mostly gone, I'm afraid, and these subdirs are client names. My decision, but an old one that I can't really change now. So this seems like it would work fine for me with the mapping file. But if you wouldn't mind, can you walk me through what the file would look like?
Ah yes, I'm familiar with finding that I was not entirely sane in times long past and that I have to live with the consequences.
The scheme I have in mind, assuming that pathnames do not include spaces, is very simple:
/path/to/dir1/sub1 /path/to/dir2/subsub1
/path/to/dir1/sub2 /paht/to/dir2/semisub-2
That is, it contains the name of the subdirectory under dir1
and the name under dir2
.
The way you use it is then similar to this (being careful to do vaguely appropriate error checking):
while read src tgt
do
[ ! -d "$src" ] && { echo "$0: Not a directory: $src" 1>&2; continue; }
[ ! -d "$tgt" ] && { echo "$0: Not a directory: $tgt" 1>&2; continue; }
echo "Mapping files under $src to $tgt"
(
if cd "$src"
then cp -rf . "$tgt"
else echo "$0: Oops! cd $src failed" 1>&2
fi
)
done < /path/to/dir1/directory-mapping-file
Obviously, you can locate the directory mapping file anywhere convenient. Note that unless you are careful (as shown) to use the correct relative names, you can end up with unwanted directory hierarchies under your target directory. Definitely experiment and double-check that the script gives you what you need. Also note that the cd
command is done in an explicit sub-shell (the ( ... )
notation. This isolates the change of directory to the sub-shell, which is generally more resilient that doing the cd
in the parent shell. If the error message from the cd
command is sufficient, then the else
clause is not needed, and you could reduce that all to:
cd "$src" && cp -rf . "$tgt"
Note, too, that I quote the names; that may be old-school shell scripting, but if you ever change to a system where pathnames contain spaces, it is easier to get scripts to work sanely with a variety of shells if you enclose the names in quotes at all times.
A nice refinement is to allow comments and blank lines in the file:
sed -e 's/#.*//' -e '/^[ \t]*$/d' /path/to/dir1/directory-mapping-file |
while read src tgt
do
...as above...
done
Note that the \t
probably needs to be an actual tab (but it is more than a little difficult to show a tab in Markdown). I have a script I call nbncl (non-blank, non-comment lines) which encapsulates the logic of the sed
command shown (though it is actually a non-minimal Perl script). Using that gives:
nbncl /path/to/dir1/directory-mapping-file |
while read src tgt
...as before...
Upvotes: 1
Reputation: 7153
You can do this with rsync
with something like:
rsync -av --exclude abberation dir1/ dir2
The trailing slash on dir1/
is important as it causes rsync
to copy the contents of dir1
, not the directory itself. If you want to delete files not in the original tree, use --delete
, otherwise, they will be left alone.
Upvotes: 2