1
votes

I am trying to rsync multiple directories in a single call inside of a bash script and am running into trouble with the syntax for quoting the paths.

Here is what I am trying:

backuppath='/path/to/backup/folders/'
declare -a backupitems=('folder1' 'folder2')
backupitems=(${backupitems[@]/#/\"$backuppath})
backupitems=(${backupitems[@]/%/\"})
backup=${backupitems[@]}

rsync ${backup} /path/to/destination

I get a link_stat error saying no such file or directory for "/path/to/current/directory"/path/to/backup/folders/folder1"" and then the same error for folder2. So it seems like it is generating the quoted path like I want it to, but rsync is interpreting the paths as relative and adding on the path to the current directory at the front. rsync works correctly if I do

backuppath='path/to/backup/folders/folder1'
rsync "${backuppath}" /path/to/destination

putting the quotes into the rsync command, but I can't do this with multiple folders within one variable because it treats the multiple directories as a long single path. I got the script to work using the second method by looping over the folders and calling rsync on each one, but this method is slightly more awkward because of the way other parts of the script handle the folders so I would like to get the first method to work if there is a quick fix.

Edit:

For the top version above, with no spaces in any of the directory names, I see the following command using set -vx: rsync '"/path/to/backup/folders/folder1"' '"/path/to/backup/folders/folder2"' /path/to/destination

and I get the following error message:

rsync: link_stat "/path/to/current/directory/"/path/to/backup/folders/folder1"" failed: No such file or directory (2)

If I use the version suggested by @kdubs, then everything works when there are no spaces in the paths.

When there are spaces in the path, kdubs version results in the command:

rsync /path/to/back up/folders/folder1 /path/to/back up/folders/folder2 /path/to/destination

and the errors:

rsync: link_stat "/path/to/back" failed: No such file or directory (2)
rsync: link_stat "/path/to/back up/up/folder1" failed: No such file or directory (2)

My first version needs an additional tweak to work with spaces because expanding the array with [@] and creating a new array by enclosing the expansion with () causes the spaces in the path to break up the path into multiple array elements (see Tonin's answer below).

3
good question, appreciate sample code. BUT.. will be much easier to diagnose your problem with the actual error messages inserted and formatted as code. Also try wrapping your call to rsync with set -vx; rsync ... ; set +vx. Good luck. - shellter

3 Answers

4
votes

Embedding quotes in your variables doesn't do anything useful. The best way to do this sort of thing is to put double-quotes around your variables when expanding them, and expand arrays as "${arrayname[@]}" -- that way bash will treat each element of the array as a separate word, even if they contain spaces or other shell metacharacters. The tricky thing is prefixing each element of the array with $backuppath, but you have the right approach to that (other than putting quotes around it, rather than in the value):

backuppath='/path/with spaces/'
backupitems=('folder 1' 'folder 2')   # declare -a is optional
backupitempaths=("${backupitems[@]/#/$backuppath}")

rsync "${backupitempaths[@]}" /path/to/destination
1
votes

The issue in your script lies in the way bash defines the elements of an array. Elements are separated by spaces. So when you re-assign the modified array elements back to the array, you're actually creating more elements than you had in the first place. You can see that by adding an echo ${#backupitems[@]} after the array declaration and each assignment.

And at the end, when you flatten the array in the $backup variable, bash is even worsening the situation by making rsync believe it's given relative paths.

To solve this, I'll use the following script which works with spaces in file names (or anywhere in the path):

backuppath='/path/to/backup/folders/'
declare -a backupitems=('folder 1' 'folder 2')
PIFS=$IFS
# prevents creating new elements from the original array
IFS=''
backupitems=(${backupitems[@]/#/$backuppath})

rsync ${backupitems[@]} /path/to/destination
IFS=$PIFS

The script redeclares the IFS variable which changes bash behavior regarding records separators when creating an array. There is a slight difference from your script though, in that the $backup variable cannot be used anymore, otherwise you'll flatten the array before actually putting it to use. Let's hope it was not a requirement.

0
votes

Get rid of those quotes:

backuppath='/path/to/backup/folders/'
declare -a backupitems=('folder1' 'folder2')
backupitems=(${backupitems[@]/#/$backuppath})
backup=${backupitems[@]}

rsync ${backup} /path/to/destination