1

I am building a script that should duplicate the current users home directory to an arbitrarily selected directory, linking to the most recently created duplicate in the same directory, if any, using rsync.

The command is built and executed the following way:

...
[ -z $LAST_SNAPSHOT ] && LINK_DEST="" || LINK_DEST="--link-dest \"$BACKUP_ROOT/$LAST_SNAPSHOT\""
...

/usr/bin/rsync $OPTS $EXCLUDES $LINK_DEST "$MASTER/" "$NEW_SNAPSHOT"

When I run the script rsync presents me with the following error:

--link-dest arg does not exist: "/home/backuptest/dest/backuptest/20180619_134044"

This would be expected if the directory actually did not exist but it does.

If I remove the quotes in the code building the $LINK_DEST variable, yielding the following code

[ -z $LAST_SNAPSHOT ] && LINK_DEST="" || LINK_DEST="--link-dest $BACKUP_ROOT/$LAST_SNAPSHOT"

then rsync does not complain. I do wish to guard against any whitespace in the path so I really need the double qoutes though.

I fail to grok where the error lies. Is it with how i call rsync or do I misunderstand the shell (bash) in some way?

1

The escaped double quotes are being treated as literal characters of the filename. Since you're using bash you can use its arrays to handle this.

linkDest=()
[[ -n "$LAST_SNAPSHOT" ]] && linkDest+=('--link-dest' "$BACKUP_ROOT/$LAST_SNAPSHOT")
...

rsync $OPTS $EXCLUDES "${linkDest[@]}" "$MASTER/" "$NEW_SNAPSHOT"

The quoted "${linkDest[@]}" disappears entirely if it's empty. Otherwise it's expanded into a quoted list of its values. If $OPTS and $EXCLUDES are also lists I'd use the same mechanism for those.

1

The issue is that you've made the double quotes part of the filename.

Use an array to hold the options to rsync.

In a /bin/sh shell (would also work in bash):

# options that are always set
set -- --archive --verbose

# add --link-dest=DIR if needed
if [ -n "$LAST_SNAPSHOT" ]; then
    set -- "$@" --link-dest="$BACKUP_ROOT/$LAST_SNAPSHOT"
fi

# add some --excludes
set -- "$@" --exclude='*.ext' --exclude='dir/***'

# call rsync
rsync "$@" "$MASTER/" "$NEW_SNAPSHOT"

This makes use of the only array available in a POSIX shell (unless extended, like bash and others), the array of positional parameters. By setting the entries in this array (using set) and adding on to it as needed, we can be sure to handle quoted arguments to rsync (or any command) appropriately. In the call to rsync, we then use "$@" which will ensure that the entries in the array are individually double quoted and therefore protected from word splitting and filename generation by the shell.

The equivalent thing but using a bash array:

# options that are always set
opts=( --archive --verbose )

# add --link-dest=DIR if needed
if [ -n "$LAST_SNAPSHOT" ]; then
    opts+=( --link-dest="$BACKUP_ROOT/$LAST_SNAPSHOT" )
fi

# add some --excludes
opts+=( --exclude='*.ext' --exclude='dir/***' )

# call rsync
rsync "${opts[@]}" "$MASTER/" "$NEW_SNAPSHOT"
  • If I understand yours and roaimas answers correctly then making the double quotes part of the string leads to them being seen as a part of the parameter while supplying them in the actual call to rsync then they are not and that this is because of how the shell works. Am I correct in my interpretation? – fuumind Jun 20 '18 at 11:03
  • 1
    @fuumind The quotes are needed whenever the shell would otherwise do word splitting or filename generation (expanding wildcards). By adding a set of double quotes the way you do, you make them part of the filename that rsync will try to synchronise. – Kusalananda Jun 20 '18 at 11:22
-1

The difference is that escaping the quote \" turns it into a string character.

So the error is probably correct:

"/home/backuptest/dest/backuptest/20180619_134044"

Where including the quotes are part of the name is different than

/home/backuptest/dest/backuptest/20180619_134044

Which is what you want The recommended way to resolve this is with ${} variables for example: $ myvar=var $ echo "\"$myvar\"" "var" $ echo "${myvar}" var

So something like "--link-dest "${BACKUP_ROOT}"/"${LAST_SNAPSHOT}"" Should get you closer.

For more on this: https://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-around-shell-variables and http://wiki.bash-hackers.org/syntax/pe#simple_usage

  • 1
    There is absolutely no difference between $variable and ${variable}, except when immediately followed by a character that may be part of a variable name, as in "${variable}x". – Kusalananda Jun 19 '18 at 16:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.