3

NB: though the question below features rsync, it is not a question about rsync; it is a question about zsh arrays.


If I initialize the variable EXCLUDES like this

EXCLUDES=( --exclude=/foo --exclude=/bar --exclude=/baz )

then, when I run the command

rsync -a $EXCLUDES / /some/target

...then I see that /foo, /bar, and /baz are indeed not copied to /some/target.

But now, suppose that EXCLUDE_ITEMS is something like this

EXCLUDE_ITEMS=( /foo /bar /baz )

...and I initialize EXCLUDE like this

EXCLUDES=()
for item in $EXCLUDE_ITEMS
do
    EXCLUDES+=( "--exclude='$item'" )
done

...or like this

EXCLUDES=( $(for item in $EXCLUDE_ITEMS; do echo "--exclude='$item'"; done) )

...then in either case, after I run

rsync -a $EXCLUDES / /some/target

...I now find that the excluded directories have not been excluded from the transfer.

Since the command lines (as typed on the terminal) are all identical, I must conclude that there is a difference between the explicitly initialized EXCLUDES from those that get initialized by iterating over EXCLUDE_ITEMS, but I cannot figure out what it is.

How can I initialize EXCLUDE from the items in EXCLUDE_ITEMS so that the latter indeed get excluded when I run

rsync -a $EXCLUDES / /some/target

PS: In fact, if I run

eval "rsync -a $EXCLUDES / /some/target"

...where EXCLUDES has been initialized in either of the for-loop-based ways shown above, then the directories named in EXCLUDES are indeed excluded, as desired.

kjo
  • 15,339
  • 25
  • 73
  • 114

1 Answers1

3

The problem is that by writing "--exclude='$item'", you're putting single quotes in the exclude pattern. So you're excluding '/foo' and so on, but you probably don't have any files with names starting and ending with single quotes.

You need to write

EXCLUDES+=( "--exclude=$item" )

or, since this is zsh, you don't need double quotes: just write

EXCLUDES+=( --exclude=$item )

or, since this is zsh, you don't need a loop: just use the ^ parameter expansion option:

rsync -a --exclude=$^EXCLUDE_ITEMS …

In ksh or bash, you would need the loop and double quotes throughout:

EXCLUDES=()
for item in "${EXCLUDE_ITEMS[@]}";  do
  EXCLUDES+=( "--exclude=$item" )
done
rsync -a "${EXCLUDES[@]}" …
  • Even though I've been using zsh for years (decades even), I have never managed to fully understand quoting... Setting EXCLUDES=( --exclude='/foo' --exclude='/bar' --exclude='/baz' ) causes no problem, despite the single quotes. But, as you point out, using EXCLUDES+=( "--exclude='$item'" ) does lead to problems. I realize that it all has to do with the relative timing of the various expansions and whatnot that happen during zsh's parsing. This is what I cannot keep track of, and is at the root of 90% of my problems with zsh. – kjo Mar 06 '16 at 20:41
  • @kjo In "--exclude='$item'", the single quotes are between double quotes. Since they're quoted, they don't have a special meaning, they're just the ' character. – Gilles 'SO- stop being evil' Mar 06 '16 at 21:03
  • Yes, thank you, I see that. I can rationalize these things after-the-fact (i.e. when I know where to focus), but to sort out all of the possible ins and outs ahead of time is, for me, the equivalent of multiplying two 5-digit numbers in my head; it's beyond what I can do easily. In no other programming context do I have such difficulties. For me shell quoting is pure hell. – kjo Mar 06 '16 at 22:13