I want to pass $home as a list of directories to be excluded when running rsync. The file:
cat backup_system.sh
#!/bin/bash
set -x
home=''
home=$home'"/home/ravas/Documents/*"',
home=$home'"/home/ravas/Downloads/*"',
home=$home'"/home/ravas/pCloudDrive/*"',
home=$home'"/home/ravas/pCloudLocal/*"',
home=$home'"/home/ravas.old/*"'
echo $home
sudo rsync -aAXv / --exclude={$home,"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} /run/media/ravas/system_backup
The backup runs fine without the $home variable, but if I add it, it doesn't have any effect.
The last two lines of the output show that the directories listed in $home are not expanded with --exclude, whereas the others are.
Needless to say, I'm not very good at bash, and any help would be much appreciated!
./backup_system.sh
+ home=
+ home='"/home/ravas/Documents/*",'
+ home='"/home/ravas/Documents/*","/home/ravas/Downloads/*",'
+ home='"/home/ravas/Documents/*","/home/ravas/Downloads/*","/home/ravas/pCloudDrive/*",'
+ home='"/home/ravas/Documents/*","/home/ravas/Downloads/*","/home/ravas/pCloudDrive/*","/home/ravas/pCloudLocal/*",'
+ home='"/home/ravas/Documents/*","/home/ravas/Downloads/*","/home/ravas/pCloudDrive/*","/home/ravas/pCloudLocal/*","/home/ravas.old/*"'
+ echo '"/home/ravas/Documents/*","/home/ravas/Downloads/*","/home/ravas/pCloudDrive/*","/home/ravas/pCloudLocal/*","/home/ravas.old/*"'
"/home/ravas/Documents/*","/home/ravas/Downloads/*","/home/ravas/pCloudDrive/*","/home/ravas/pCloudLocal/*","/home/ravas.old/*"
+ sudo rsync -aAXv /
'--exclude="/home/ravas/Documents/*","/home/ravas/Downloads/*","/home/ravas/pCloudDrive/*","/home/ravas/pCloudLocal/*","/home/ravas.old/*"'
'--exclude=/dev/*' '--exclude=/proc/*' '--exclude=/sys/*' '--exclude=/tmp/*' '--exclude=/run/*' '--exclude=/mnt/*' '--exclude=/media/*' --exclude=/lost+found** /run/media/ravas/system_backup
The basic problem is the order that bash parses various things in your command is all wrong for what you're trying to do. Specifically, it parses (and applies and removes) quotes & escapes, then expands {this,that,theother}
lists, then expands variable references (like $home
). Net result is that by the time $home
expands to a comma-separated list of quoted elements, it's too late for either the quotes or commas to have their intended effect.
As usual in cases like this, arrays are the best way to handle this:
homearray=()
homearray+=("/home/ravas/Documents/*")
homearray+=("/home/ravas/Downloads/*")
homearray+=("/home/ravas/pCloudDrive/*")
homearray+=("/home/ravas/pCloudLocal/*")
homearray+=("/home/ravas.old/*")
# Alternate short form:
# homearray=(/home/ravas/{Documents,Downloads,pCloudDrive,pCloudLocal}/"*" "/home/ravas.old/*")
sudo rsync -aAXv / "${homearray[@]/#/--exclude=}" --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} /run/media/ravas/system_backup
Explanation: "${homearray[@]/#/--exclude=}"
does three things; first, the [@]
makes it expand to all of the elements of the array; then the /#/--exclude=
"replaces" the beginning of each element with "--exclude=" (essentially, it prepends "--exclude=" to each element), and finally the double-quotes around it keep the wildcards ("*") from expanding and wreaking havoc.
The solution to almost all multiple-argument-related problems is to use an array:
exclusions=(
--exclude "/home/ravas/Documents/*"
--exclude "/home/ravas/Downloads/*"
--exclude "/home/ravas/pCloudDrive/*"
--exclude "/home/ravas/pCloudLocal/*"
--exclude "/home/ravas.old/*"
--exclude "/dev/*"
--exclude "/proc/*"
--exclude "/sys/*"
--exclude "/tmp/*"
--exclude "/run/*"
--exclude "/mnt/*"
--exclude "/media/*"
--exclude "/lost+found"
)
sudo rsync -aAXv / "${exclusions[@]}" /run/media/ravas/system_backup
If you aren't using standard input for anything else, you can use the --exclude-from
option with a here document to shorten this a bit:
sudo rsync -aAXv / --exclude-from - /run/media/ravas/system_backup <<EOF
/home/ravas/Documents/*
/home/ravas/Downloads/*
/home/ravas/pCloudDrive/*
/home/ravas/pCloudLocal/*
/home/ravas.old/*
/dev/*
/proc/*
/sys/*
/tmp/*
/run/*
/mnt/*
/media/*
/lost+found
EOF
String handling in Bash is rarely straightforward, but here's something that sort of works...
( list=abc,'"def\ ghi"',jkl ; eval echo --exclude={$list} )
output:
--exclude=abc --exclude=def\ ghi --exclude=jkl
See eval command in Bash and its typical uses