Bash – How to properly escape an rsync exclude pattern used in a bash script

bashrsync

Script:

#!/bin/bash

site=$1
remote_host=$2
new_site=${3:-$1}

cmd="rsync -rlpuvz -e ssh /www/$site/ $remote_host:/www/$new_site --force --delete --exclude=\"site/web_sitemap_*.xml.gz\""
echo $cmd
$cmd

Script output:

[xxx@xxx ~]$ rsync -rlpuvz -e ssh /www/xxx/ xxx:/www/xxx --force --delete --exclude="site/web_sitemap_*.xml.gz"

sending incremental file list
deleting site/web_sitemap_ff3abe06_000.xml.gz

It seems to be treating the quotes \" as literal " filename/pattern characters. I get the same thing with single-quotes (unescaped) ' as in "--exclude='site/web_sitemap_*.xml.gz'". HOWEVER, If we try the same thing without any type of embedded quotes it works! Unquoted version:

[xxx@xxx ~]$ touch /www/xxx/site/web_sitemap_ff3abe06_000.xml.gz

[xxx@xxx ~]$ rsync -rlpuvz -e ssh /www/xxx/ xxx:/www/xxx --force --delete --exclude=site/web_sitemap_*.xml.gz

sending incremental file list

sent 41624 bytes  received 290 bytes  7620.73 bytes/sec
total size is 18233892  speedup is 435.03

Not using quotes seems like the answer but then how do I protect against shell expansion or interpretation of special characters in cases were the rsync exclude pattern contains symbols that would normally be expanded or interpreted by bash (like : or ; or \ or [) ?

Best Answer

You should read BashFAQ/050 aka I'm trying to put a command in a variable, but the complex cases always fail!.

A TL;DR:

This fails because [...] the [...] quotes inside the variable are literal; not syntactical.

Word splitting is also a problem when trying to store a command in a variable. Although not with your immediate example this is something one has to be aware of.

Once you have read #050 you can re-evaluate if you really need to do this. If you still think you do and choose to disregard the advice against it you will want to store your command in an array:

cmd=( rsync -rlpuvz -e ssh "/www/${site}/" "${remote_host}:/www/${new_site}" --force --delete --exclude="site/web_sitemap_*.xml.gz" )
"${cmd[@]}"