Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

share|improve this question
up vote 3 down vote accepted

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[@]}" …
share|improve this answer
    
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 23 hours ago
    
@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 22 hours ago
    
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 21 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.