I have a Bash script in which I call rsync
in order to perform a backup to a remote server. To specify that my Downloads folder be backed up, I'm passing "'${HOME}/Downloads'"
as an argument to rsync
which produces the output:
rsync -avu '/Volumes/Norman Data/Downloads' me@example.com:backup/
Running the command with the variable expanded as above (through the terminal or in the script) works fine, but because of the space in the expanded variable and the fact that the quotes (single ticks) are ignored when included in the variable being passed as part of an argument (see here), the only way I can get it not to choke on the space is to do:
stmt="rsync -avu '${HOME}/Downloads' me@examle.com:backup/"
eval ${stmt}
It seems like there would be some vulnerabilities presented by running eval
on anything not 100% private to that script. Am I correct in thinking I should be doing it a different way? If so, any hints for a bash-script-beginner would be greatly appreciated.
** EDIT ** - I actually have a bit more involved use case than. the example above. For the paths passed, I have an array of them, each containing spaces, that I'm then combining into 1 string kind of like
include_paths=(
"'${HOME}/dir_a'"
"'${HOME}/dir_b' --exclude=video"
)
for item in "${include_paths[@]}"
do
inc_args="${inc_args}" ${item}
done
inc_args
evaluates to '/Volumes/Norman Data/me/dir_a' '/Volumes/Norman Data/me/dir_b' --exclude=video
which I then try to pass as an argument to rsync
but the single ticks are read as literals and it breaks after the 1st /Volumes/Norman
because of the space.
rsync -avu "${inc_args}" me@example.com:backup/
Using eval seems to read the single ticks as quotes and executes:
rsync -avu '/Volumes/Norman Data/me/dir_a' '/Volumes/Norman Data/me/dir_b' --exclude=video me@example.com:backup/
like I need it to. I can't seem to get any other way to work.
** EDIT 2 - SOLUTION **
So the 1st thing I needed to do was modify the include_paths
array to:
- remove single ticks from within double quoted items
- move any path-specific flags (ex.
--exclude
) to their own items directly after the path it should apply to
I then built up an array containing the rsync
command and its options, added the expanded include_paths
and exclude_paths
arrays and the connection string to the remote host.
And finally expanded that array, which ran my entire, properly quoted rsync
command. In the end the modified array include_paths
is:
include_paths=(
"${HOME}/dir_a"
"${HOME}/dir_b"
"--exclude=video"
"${HOME}/dir_c"
)
and I put everything together with:
cmd=(rsync -auvzP)
for item in "${exclude_paths[@]}"
do
cmd+=("--exclude=${item}")
done
for item in "${include_paths[@]}"
do
cmd+=("${item}")
done
cmd+=("me@example.com:backup/")
set -x
"${cmd[@]}"