rsync with list of files as variables

I have two files I want to be rsynced from remote samba share based on date names this way:

older="/path/to/file/"$(date -d '2 days ago' '+%Y%m%d')*.pdf
old="/path/to/file/"$(date -d yesterday '+%Y%m%d')*.pdf

rsync -ahvz "$old $older" /destination

Seems that filename variables are ok but rsync gives me an error:

rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1207) [sender=3.1.3]

if I check file existence, there's no issue:

ls -lah $older
rwxrwxrwx 553M forge 21 Aug 11:57  20210821.pdf

Solution 1:

The problem is your quoting. The double-quotes around the filepath variables ("$old $older") tell the shell to treat both as a single item, and also prevents it from expanding the wildcards (*) into the actual name(s) of matching files. So it's looking for a single file at the exact path "/path/to/file/20210822*.pdf /path/to/file/20210821*.pdf" (and to be clear, that path includes a directory named exactly "20210822*.pdf ", including a literal asterisk character and a space at the end of the name).

Now, if the paths don't include any actual funny characters (like spaces in file/directory names), you could just remove the double-quotes entirely, so the shell will treat the variables as separate items and also expand the wildcards:

rsync -ahvz $old $older /destination

But if you're like me, and work in an environment where spaces in file/directory names are a thing that happens, this won't work because the paths will get split into multiple items ("words") on those spaces (or certain other characters). In that case, you need double-quotes around the parts that shouldn't be word-split, and not have double-quotes around the wildcards, and you can't partially-quote a variable.

(People often try to solve problems like this by embedding quotes and/or escapes in the variable's value to try to control splitting and expansion. It doesn't work. Don't try to make it work, it's just the wrong way to do things.)

In bash (but not more basic shells that don't have arrays), the better way to handle this is to expand the wildcards immediately, and store the list of matching names as an array:

older=("/path/to/file/$(date -d '2 days ago' '+%Y%m%d')"*.pdf)
old=("/path/to/file/$(date -d yesterday '+%Y%m%d')"*.pdf)

rsync -ahvz "${old[@]}" "${older[@]}" /destination

Here, the parentheses in the assignment make older and old arrays instead of plain variables (even if there's only one matching file for each, then they're just one-element arrays), and the "${arrayname[@]}" syntax tells the shell to get all elements of the array, but not to do anything stupid with them.

Also, note that in the array declaration, I double-quoted the fixed prefix of the paths, and also the $(date ...) part -- it's generally good scripting hygiene to have double-quotes around command substitutions (and variables references), to avoid any unexpected word splitting etc.