提问人:Aaron 提问时间:9/20/2022 更新时间:9/21/2022 访问量:117
Bash - 扩展命令参数的变量会导致引用地狱
Bash - Expanding variables for command arguments leads to quoting hell
问:
所以,我正在编写一个命令的脚本。我一整天都在摆弄。是时候寻求帮助了。rsync
我的问题似乎是引用,但对我来说并不明显到底出了什么问题。此代码片段:
#!/bin/bash
sudo -v
COMMAND=`basename ${0}`
SRC='/etc'
SRC=`realpath ${SRC}` # make relative paths absolute
SRC=${SRC}/ # force rsync to disallow symlinks to the parent path
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 "${DIR}"
fi
done
RSYNC_OPTS=""
#--dry-run, -n
RSYNC_OPTS=${RSYNC_OPTS}" --dry-run"
# --recursive, -r recurse into directories
RSYNC_OPTS=${RSYNC_OPTS}" --recursive"
#--filter=RULE, -f add a file-filtering RULE
RSYNC_OPTS=${RSYNC_OPTS}" --filter='dir-merge,p- .gitignore'"
# --checksum, -c skip based on checksum, not mod-time & size
RSYNC_OPTS=${RSYNC_OPTS}" --checksum"
echo "rsync ${RSYNC_OPTS} '${SRC}' '${DEST}'" | sudo tee "${LOG_FILE}"
echo --------
echo --------
echo
echo
set -x
sudo rsync "${RSYNC_OPTS} '${SRC}' '${DEST}'"
生成以下内容:
Creating '/tmp/backup prep'
mkdir: cannot create directory ‘/tmp/backup prep’: File exists
Creating '/tmp/backup prep/tmp'
mkdir: cannot create directory ‘/tmp/backup prep/tmp’: File exists
Creating '/tmp/backup prep/tmp'
mkdir: cannot create directory ‘/tmp/backup prep/tmp’: File exists
rsync --dry-run --recursive --filter='dir-merge,p- .gitignore' --checksum '/etc/' '/tmp/backup prep/etc'
--------
+ sudo rsync ' --dry-run --recursive --filter='\''dir-merge,p- .gitignore'\'' --checksum '\''/etc/'\'' '\''/tmp/backup prep/etc'\'''
rsync: [sender] change_dir "/home/aaron/bin/ --dry-run --recursive --filter='dir-merge,p- .gitignore' --checksum '/etc/' '/tmp/backup prep" failed: No such file or directory (2)
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1333) [sender=3.2.3]
并且似乎什么也没做。
问题是,如果我执行回显的命令行,一切似乎都按预期工作。sudo rsync --dry-run --recursive --filter='dir-merge,p- .gitignore' --checksum '/etc/' '/tmp/backup prep/etc'
炮弹扩张让我每一次血腥。如果有一个命令可以告诉你到底发生了什么,那就太好了。
答:
3赞
Aaron
9/21/2022
#1
这是我完成的剧本。非常感谢对我问题的评论者。在你的提示下,我能够取得进步。
就其价值而言,目标是定期备份正在使用 .git 存储库的 /etc 并保留 .git 存储库,但忽略 .gitignore 中的内容etckeeper
一些观察:
- 在为命令构建参数列表时,数组完全是要走的路。
- 使用数组时,shell 会漂亮地用空格引用值。Yo 可以通过在下面的脚本中使用来看到这一点。
set -x
- 使用真的很有帮助。
set -x
- 当您努力获得某些东西时,shellcheck 是一个了不起的诊断工具。
#!/bin/bash
COMMAND=$(basename "${0}")
SRC="/etc"
BACKUP_DIR="/root/backup prep"
TMP_DIR="${BACKUP_DIR}/tmp"
LOG_DIR="${TMP_DIR}"
if ! sudo -v
then
echo ${COMMAND} quitting...
exit 1
fi
SRC=$(realpath "${SRC}") # make relative paths absolute
SRC="${SRC}"/ # force rsync to disallow symlinks to the parent path
# https://www.shellcheck.net/wiki/SC2086
# Note that $( ) starts a new context, and variables in it have to
# be quoted independently
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 sudo test ! -d "${DIR}";
then
echo Creating "${DIR}"
sudo mkdir "${DIR}"
fi
done
RSYNC_OPTS=()
# ------------------------------------
# -------------Testing----------------
# ------------------------------------
#
#--dry-run, -n
#RSYNC_OPTS+=("--dry-run")
# ------------------------------------
# -------------Verbosity--------------
# ------------------------------------
#
# --verbose, -v increase verbosity
#RSYNC_OPTS+=("--verbose")
# --progress show progress during transfer
#RSYNC_OPTS+=("--progress")
# --itemize-changes, -i output a change-summary for all updates
#RSYNC_OPTS+=("--itemize-changes")
# --stats give some file-transfer stats
#RSYNC_OPTS+=("--stats")
# --human-readable, -h output numbers in a human-readable format
RSYNC_OPTS+=("--human-readable")
# --log-file=FILE log what we're doing to the specified FILE
RSYNC_OPTS+=("--log-file=${LOG_FILE}")
# --log-file-format=FMT log updates using the specified FMT
# ------------------------------------
# -------------Permissions------------
# ------------------------------------
#
# --owner, -o preserve owner (super-user only)
RSYNC_OPTS+=("--owner")
# --group, -g preserve group
RSYNC_OPTS+=("--group")
# --perms, -p preserve permissions
RSYNC_OPTS+=("--perms")
# --xattrs, -X preserve extended attributes
RSYNC_OPTS+=("--xattrs")
# --acls, -A preserve ACLs (implies --perms)
RSYNC_OPTS+=("--acls")
# ------------------------------------
# -------------Times------------------
# ------------------------------------
#
# --times, -t preserve modification times
RSYNC_OPTS+=("--times")
# --crtimes, -N preserve create times (newness)
# rsync: This rsync does not support --crtimes (-N)
# RSYNC_OPTS+=("--crtimes")
# --atimes, -U preserve access (use) times
RSYNC_OPTS+=("--atimes")
# --open-noatime avoid changing the atime on opened files
RSYNC_OPTS+=("--open-noatime")
# ------------------------------------
# -------------Where------------------
# ------------------------------------
#
# --mkpath create the destination's path component
RSYNC_OPTS+=("--mkpath")
# --recursive, -r recurse into directories
RSYNC_OPTS+=("--recursive")
# --links, -l copy symlinks as symlinks
RSYNC_OPTS+=("--links")
# Rsync can also distinguish "safe" and "unsafe" symbolic links. An
# example where this might be used is a web site mirror that wishes to
# ensure that the rsync module that is copied does not include symbolic
# links to /etc/passwd in the public section of the site. Using
# --copy-unsafe-links will cause any links to be copied as the file they
# point to on the destination. Using --safe-links will cause unsafe
# links to be omitted altogether. (Note that you must specify --links
# for --safe-links to have any effect.)
# --copy-unsafe-links where the link would point outside of the new tree, copy the file
RSYNC_OPTS+=("--copy-unsafe-links")
# --hard-links, -H preserve hard links
RSYNC_OPTS+=("--hard-links")
# --one-file-system, -x don't cross filesystem boundaries
RSYNC_OPTS+=("--one-file-system")
# ------------------------------------
# -------------Exclusions-------------
# ------------------------------------
#
#--filter=RULE, -f add a file-filtering RULE
RSYNC_OPTS+=("--filter=dir-merge,p- .gitignore")
# --delete-excluded also delete excluded files from dest dirs
RSYNC_OPTS+=("--delete-excluded")
#--delete-after receiver deletes after transfer, not during
RSYNC_OPTS+=("--delete-after")
# --force force deletion of dirs even if not empty
RSYNC_OPTS+=("--force")
# --update, -u skip files that are newer on the receiver
#RSYNC_OPTS=${RSYNC_OPTS}" --update")
# ------------------------------------
# -------------Misc-------------------
# ------------------------------------
#
# --protect-args, -s no space-splitting; wildcard chars only
# This option sends all filenames and most options to the remote rsync
# without allowing the remote shell to interpret them.
RSYNC_OPTS+=("--protect-args")
# --checksum, -c skip based on checksum, not mod-time & size
RSYNC_OPTS+=("--checksum")
# --temp-dir=DIR, -T create temporary files in directory DIR
RSYNC_OPTS+=("--temp-dir=${TMP_DIR}")
#echo sudo rsync "${RSYNC_OPTS[@]}" "'${SRC}'" "'${DEST}'"
#echo
#set -x
sudo rsync "${RSYNC_OPTS[@]}" "${SRC}" "${DEST}"
评论
-d "'${DIR}'"
$DIR
rsync "${RSYNC_OPTS} '${SRC}' '${DEST}'"
rsync