How append --exclude argument dynamically to restic command?


I built a simple generic wrapper script to simplify some of the most needed restic functionality.

E.g. passing an exclude list as environment variable:

export EXCLUDES="--exclude=~/restic/sources/excluded --exclude=~/sources/excluded2"

, which is then included in inside this wrapper (simplified case):

restic backup $EXCLUDES ~/restic/sources

This works, but I still have a problem for file paths with spaces.
For a subfolder ~/sources/excluded 3, both of the following won’t parse properly:

export EXCLUDES="--exclude=\"/home/user/restic/sources/excluded 3\"" # (1)
export EXCLUDES="--exclude='/home/user/restic/sources/excluded 3'"   # (2)

With set -x, the relevant part of restic command is logged like this:

'--exclude="/home/user/restic/sources/excluded' '3"'       # (1)
'--exclude='\''/home/user/restic/sources/excluded' '3'\'''  # (2)

, whose quotes I don’t really understand.

I also came across bash - Why does my shell script choke on whitespace or other special characters? - Unix & Linux Stack Exchange, but still cannot grasp above shell rules for quotes.

How might I change EXCLUDES, so an argument with spaces is interpreted correctly?

For sure there is --exclude-file, but I am mostly interested, how the shell works here.

Thanks !

Did you try to simply make it export EXCLUDES="--exclude=/home/user/restic/sources/excluded\ 3" or export EXCLUDES="--exclude=/home/user/restic/sources/excluded\\ 3"?

@rawtaz thanks for the hint - yeah, I had tried the first one before. The second one unfortunately doesn’t work as well.

For the test case I do a full clean of the repository, initialize it and trigger the backup job. For both I get

new       /home/user/restic/sources/excluded 3/

Apparently the quotes inside $EXCLUDES are already escaped, which means that they won’t escape anything. I’m not sure whether there’s a way to avoid that.

$ set -x
$ cnt() { echo $#; }
+ cnt '--exclude=/home/user/restic/sources/excluded\' 3
+ echo 2

My understanding here is that $EXCLUDE is substituted in the command line and is then split a whitespace characters (remember that $EXCLUDE is not quoted here).

With shell features probably the only clean way is to use an array:

$ EXCLUDES=( "/home/user/restic/sources/excluded 3" )
$ cnt "${EXCLUDES[@]}"
+ cnt '/home/user/restic/sources/excluded 3'
+ echo 1
1 Like

Your explanation absolutely makes sense, these quotes must already be escaped.
This also makes me understand my above set -x output.

Using arrays or --exclude-file probably is the best idea, which to my surprise works with an array argument passed to a different script/function. Thanks!