Bash脚本变量展开用于命令参数引发引号问题求助
解决rsync备份脚本的引号与参数传递问题
问题根源
- 参数存储方式错误:你用普通字符串
RSYNC_OPTS存储rsync选项,当选项里包含带引号的规则(比如--filter='dir-merge,p- .gitignore'),最后执行时把整个字符串套进双引号,shell会把这一整串当成单个参数传给rsync,导致rsync误以为这是要同步的源路径,所以报错找不到目录。 - 目录存在性判断错误:
if [[ ! -d "'${DIR}'" ]]这行里,你给目录路径加了额外的单引号,导致shell判断的是'/tmp/backup prep'(带单引号的路径),而实际目录是/tmp/backup prep,所以每次都认为目录不存在,重复执行mkdir。
修复后的脚本
#!/bin/bash sudo -v COMMAND=$(basename "$0") SRC='/etc' SRC=$(realpath "$SRC") # 转换为绝对路径 SRC="${SRC}/" # 强制rsync不允许指向父目录的符号链接 if [[ ! -d "$SRC" ]]; then echo "Use $COMMAND to backup directories only" exit 1 fi BACKUP_DIR='/tmp/backup prep' TMP_DIR="${BACKUP_DIR}/tmp" DEST="${BACKUP_DIR}/$(basename "$SRC")" LOG_DIR="$TMP_DIR" LOG_FILE="${LOG_DIR}/${COMMAND}-$(date +%Y-%b-%d-%H-%M-%S-%N).log" for DIR in "$BACKUP_DIR" "$TMP_DIR" "$LOG_DIR"; do if [[ ! -d "$DIR" ]]; then echo "Creating $DIR" sudo mkdir -p "$DIR" fi done # 用数组存储rsync选项,正确保留每个参数的边界 RSYNC_OPTS=( --dry-run --recursive --filter='dir-merge,p- .gitignore' --checksum ) # 输出命令到日志 echo "sudo rsync ${RSYNC_OPTS[*]} '$SRC' '$DEST'" | sudo tee "$LOG_FILE" echo -------- echo -------- echo echo set -x # 展开数组,每个元素作为独立参数传递 sudo rsync "${RSYNC_OPTS[@]}" "$SRC" "$DEST"
关键改动说明
- 改用数组存储选项:
RSYNC_OPTS=(...)数组能正确区分每个rsync参数,不管参数里有没有空格或引号,展开时都会保持每个参数的独立性。 - 修正目录判断:去掉
[[ ! -d "'${DIR}'" ]]里的额外单引号,直接用[[ ! -d "$DIR" ]],正确判断实际目录是否存在。 - 执行rsync时正确展开数组:
"${RSYNC_OPTS[@]}"会把数组的每个元素作为单独的参数传给rsync,和你手动执行命令的效果完全一致。 - 替换反引号为$():
$(basename "$0")比反引号的可读性更好,嵌套时也不容易出错。 - 添加mkdir -p:即使父目录不存在也能创建,同时避免重复创建已存在目录的错误(不过这里判断已经做了,加-p更稳妥)。
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

