Bash: need help passing a a variable to rsync

2020-04-19 06:59发布

问题:

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

回答1:

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.



回答2:

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


回答3:

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