1

I'm trying to programmatically use rsync and all is well until you have spaces in the paths. Then I have to quote the path in the script which is also fine until it's optional.

In this case --link-dest can be optional and I've tried variations to accommodate it in both cases of it being there and not but I'm having problems when paths need to be quoted.

One option would be to use a case statement and just call rsync with two different lines but there are other rsync options that may or may not be there and the combinations could add up quickly so a better solution would be nice. Since this is my machine and no one but me has access to it I'm not concerned about security so if an eval or something is the only way that's fine.

#!/bin/bash

src='/home/x/ll ll'
d1='/home/x/rsy nc/a'
d2='/home/x/rsy nc/b'

rsync -a "$src" "$d1"

# Try 1
x1='--link-dest='
x2='/home/x/rsy nc/a'
rsync -a $x1"$x2"  "$src" "$d2" 

This works until you don't have a --link-dest which will look like this:

x1=''
x2=''
rsync -a $x1"$x2"  "$src" "$d2" 

And rsync takes the empty "" as the source so it fails.

# Try 2
x1='--link-dest="'
x2='/home/x/rsy nc/a"'
rsync -a $x1$x2  "$src" "$d2" 

This fails because double quotes in x1 & x2 are taken as part of the path and so it creates a path not found error but it does of course work like this:

x1=''
x2=''
rsync -a $x1$x2  "$src" "$d2" 

I've tried a couple of other variations but they all come back to the two issues above.

Reg
  • 25
  • 4
  • 1
    You can either store the options in an array (as in joanis' answer) or use conditional expansion (see my answer [here](https://stackoverflow.com/questions/20306739/how-to-not-pass-empty-quoted-variables-as-arguments-to-commands/20306982#20306982) and section 3 of [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050#I_only_want_to_pass_options_if_the_runtime_data_needs_them)). – Gordon Davisson Jan 10 '23 at 03:03

1 Answers1

2

You can use Bash arrays to solve this kind of problem.

# inputs come from wherever
src='/home/x/ll ll'
d1='/home/x/rsy nc/a'
d2='/home/x/rsy nc/b'
x2='something'
# or
x2=''

# build the command
my_cmd=(rsync -a)
if [[ $x2 ]]; then
   my_cmd+=("--link-dest=$x2")
fi
my_cmd+=("$src" "$d2")

# run the command
"${my_cmd[@]}"

First I'm constructing the command bit by bit, with quotes carefully used to preserve multi-word options as such, storing each option/argument as an element in my array, including the rsync command itself.

Then I invoke it with the weird "${my_cmd[@]}" syntax, which means expand all the elements of the array, without re-splitting multi-word elements. Note that the quotes are important: if you remove them, the multiple word values in the array get split again and you're back to square one.

The syntax "${my_cmd[*]}" also exists, but that one joins the whole array into a single word, again now what you want. You need the [@] to get each array element expanded as its own word in the result.

Google "bash arrays" for more details.

Edit: conditional pipes

If you want to pipe the output throw some other command on some condition, you can just pipe it into an if statement. This works:

"${my_cmd[@]}" |
if [[ $log_file]]; then
   tee "$log_file"
fi

and tees the output to $log_file if that variable is defined and non-empty.

joanis
  • 10,635
  • 14
  • 30
  • 40
  • This worked perfectly. I didn't see how to also use the technique for optionally adding " | tee $log_file" or adding "2>$error_file" which would have been a bonus but it doesn't need to do those, I was just curious so gave it shot. – Reg Jan 17 '23 at 01:23
  • @Reg I don't know for sure, but I think redirections and pipes won't work in the array, they would have to be outside the array. If have to run some tests to confirm, but give it a try and see what happens. – joanis Jan 17 '23 at 02:52
  • Yeah, that was the conclusion I came to after trying it which is not a problem, I was just curious so gave it a try. – Reg Jan 18 '23 at 03:25
  • But if you want conditional pipes, you can pipe something into an if statement, though, I just tested and that works fine. – joanis Jan 18 '23 at 03:26
  • @Reg I just added an example bit of code for conditionally piping the output to tee. – joanis Jan 18 '23 at 03:29