Set permissions with rsync for all files but the root directory
Solution 1:
Another solution would be to use
rsync -av --delete a/* b/
, but this would prevent deleted files ina/
from being removed inb/
.
And then you run
rsync -rv --delete --existing --ignore-existing a/ b/
to deal with this. You don't use -a
here so the ownership of the target directory is not a problem.
From man 1 rsync
:
--existing
,--ignore-non-existing
This tellsrsync
to skip creating files (including directories) that do not exist yet on the destination. If this option is combined with the--ignore-existing option
, no files will be updated (which can be useful if all you want to do is delete extraneous files).
(Credits to this answer.)
Improvements:
-
In general
*
is not enough to match all files and directories. You also need.[!.]*
if you have dot files, and..?*
if you have file names beginning with two dots. In Bashdotglob
helps, so additional patterns are not needed:# in Bash shopt -s dotglob rsync -av --delete a/* b/
But you need to make sure that the pattern expands to something. If it doesn't,
rsync
will get its literal form and complain. Creating a dummy (temporary) file ina/
may be inelegant, but it will certainly make the pattern expand. If you remove the file before you run the secondrsync
then it will be removed from the destination as well. - If you ever decide to work from within
a/
directory, the pattern will become*
. In this case you should use double dash (--
) or modify the pattern (./*
) so file names like-n
are not treated as options.
An example solution may be like this:
#!/bin/bash
shopt -s dotglob
tmpf="$(mktemp -p a/)" || exit 202
rsync -av --delete a/* b/
rm "$tmpf"
rsync -rv --delete --existing --ignore-existing a/ b/
Notes:
- Each
rsync
generates its own exit status, you need additional logic to tell if everything was OK. -
If there are many objects in
a/
then the first invocation ofrsync
may yieldargument list too long
. Possible solutions:-
find
+xargs
. -
Processing objects one by one in a loop:
for f in a/*; do rsync -av --delete -- "$f" b/ done
(in this case use
shopt -s nullglob
beforehand and you won't even need a dummy file).Note I used
--
even though it's not necessary in this particular case (because expanded$f
must start witha/
). But let's say you modify the code and changea/*
to*
. Then you need to add--
in another line, it's very easy to miss this.
-