Take the 2-minute tour ×
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.

The script below works in bash but not in zsh. I think it is because in the variable OPTS, I am "expanding" (not sure if this is the right word) the variable $EXCLUDE, and this syntax doesn't work in zsh. How would I replace that line to make it work on zsh?

SRC="/path_to_source"
DST="/path_to_dest"
EXCLUDE=".hg"  
OPTS="-avr --delete --progress --exclude=${EXCLUDE} --delete-excluded"                                               

rsync $OPTS $SRC $DST   
share|improve this question
add comment

3 Answers

up vote 5 down vote accepted

The problem here is that $OPTS is not split into several arguments on the rsync command line. In zsh syntax, use:

rsync ${=OPTS} $SRC $DST 

(an alternative is to simulate standard shell behavior globably with the option -o shwordsplit…)

From the manpage:

One commonly encountered difference [in zsh] is that variables substituted onto the command line are not split into words. See the description of the shell option SH_WORD_SPLIT in the section 'Parameter Expansion' in zshexpn(1). In zsh, you can either explicitly request the splitting (e.g. ${=foo}) or use an array when you want a variable to expand to more than one word. See the section 'Array Parameters' in zshparam(1).

share|improve this answer
    
Is there anyway I can make it portable to bash? I want to use the same script/function to be usable in both bash and zsh. –  balki Sep 9 '11 at 14:09
    
@balki: You can start your script with set -o shwordsplit 2>/dev/null to tell zsh to use sh word splitting. Or you can use arrays, as described by Gilles. –  Stéphane Gimenez Sep 9 '11 at 14:43
add comment

The problem doesn't come from $EXCLUDE, it comes from $OPTS. Your script relies on word splitting happening to $OPTS. This is a bad idea; for example it will fail if you ever change $EXCLUDE to contain a pattern and --exclude=$EXCLUDE comes to match a file in the current directory, or if you ever change $EXCLUDE to contain whitespace.

$OPTS is a list of words, not a word. If you only need your script to work in ksh, bash and zsh, make it an array:

SRC="/path_to_source"
DST="/path_to_dest"
EXCLUDE=".hg"
OPTS=(-avr --delete --progress --exclude="$EXCLUDE" --delete-excluded)
rsync "${OPTS[@]}" "$SRC" "$DST"

If you only intend the script to work in zsh, you can simplify the last line:

rsync $OPTS $SRC $DST

If you want the script to work in every shell, you need to use the positional parameters. They are the only array available.

set -- -avr --delete --progress --exclude="$EXCLUDE" --delete-excluded
rsync "$@" "$SRC" "$DST"

If you have a shell script that you want to be able to run conveniently with zsh, put this line at the beginning — it tells zsh to act like ksh, and it's a no-op on other shells.

emulate ksh >/dev/null 2>/dev/null
share|improve this answer
add comment

To complete Stéphane Gimenez answer (I cannot comment yet), one solution is to make your script portable to bash by checking if you are using zsh and then transform $OPTS into an array like this

[ -n "${ZSH_VERSION:-}" ] && set -A OPTS ${=OPTS}

Then your rsync command will work.

share|improve this answer
add comment

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.