0

I'm writing a bash script to collect some directories (where conditions are met) and rsync those to a remote location. Overall the script looks like this;

sources=""
for d in /somewhere/* ; do 
  if $d meets condition; then
    sources="$sources $(printf %q "$d")"
  fi
done
if [ ! -z $sources ] ; then 
  rsync -vrz $sources /remote/target/
fi

Note that I'm using printf %q to escape spaces in directory names. However when there are spaces in directory names, for example when "/somewhere/dir name" met the condition, the rsync thinks that as two directories and fails to run;

(at /home/u/) $ bash script.sh

sending incremental file list
rsync: link_stat "/somewhere/dir\" failed: No such file or directory (2)
rsync: link_stat "/home/u/name" failed: No such file or directory (2)

rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2]

If I just print the rsync command by changing the last line to

echo rsync -vrz $sources /remote/target/

it looks just fine.

(at /home/u/) $ bash script.sh
rsync -vrz /somewhere/dirname /somewhere/dir\ name /remote/target

But using set -x shows something wacky going on.

(at /home/u/) $ bash script.sh
+ rsync -vrz /somewhere/dirname '/somewhere/dir\' name /remote/target
sending incremental file list
rsync: link_stat "/somewhere/dir\" failed: No such file or directory (2)
rsync: link_stat "/home/u/name" failed: No such file or directory (2)

rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2]

I also tried to use double quoted directory names instead of printf %q but it didn't work either, with a slightly different reason.

(at /home/u/) $ bash script.sh
+ rsync -vrz '"/somewhere/dirname"' '"/somewhere/dir' 'name"' /remote/target
sending incremental file list
rsync: change_dir "/home/u//"/somewhere" failed: No such file or directory (2)
rsync: change_dir "/home/u//"/somewhere" failed: No such file or directory (2)
rsync: link_stat "/home/u/name"" failed: No such file or directory (2)

sent 20 bytes  received 12 bytes  64.00 bytes/sec
total size is 0  speedup is 0.00
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2]

Where are those single quotes around some arguments coming from and what is the best way to collect directories with spaces in a single-lined variable for using as sources in cp, mv, or rsync?

krim
  • 138
  • 1
  • 6
  • See ["Why does shell ignore quoting characters in arguments passed to it through variables?"](https://stackoverflow.com/questions/12136948/why-does-shell-ignore-quotes-in-arguments-passed-to-it-through-variables) and ["Why do bash parameter expansions cause an rsync command to operate differently?"](https://stackoverflow.com/questions/29527983/why-do-bash-parameter-expansions-cause-an-rsync-command-to-operate-differently) and [BashFAQ #50: "I'm trying to put a command in a variable, but the complex cases always fail!"](http://mywiki.wooledge.org/BashFAQ/050) – Gordon Davisson Nov 25 '20 at 06:12

1 Answers1

2

Use an array instead.

sources=()
for d in /somewhere/* ; do 
  if $d meets condition; then
    sources+=("$d")
  fi
done
if [ "${sources[*]}" ]; then 
  rsync -vrz "${sources[@]}" /remote/target/
fi

The single quotes output with set -x are just for disambiguation, so you can see exactly where actual literal spaces go (as opposed to syntactic spaces which are argument separators).

tripleee
  • 175,061
  • 34
  • 275
  • 318