如何将可变数量的参数传递给shell命令?
我正在编写一个备份脚本,如下所示: 备份。sh:如何将可变数量的参数传递给shell命令?,shell,Shell,我正在编写一个备份脚本,如下所示: 备份。sh: dir="$1" mode="$2" delta="$3" for file in "$dir/backup."*".$mode.tar.gz"; do [ "$file" -nt "$ref" ] && ref="$file" done if [ "$delta" = "true" ]; then delta_cmd=-N "'$ref'" fi backup_file="$dir/backup.$(dat
dir="$1"
mode="$2"
delta="$3"
for file in "$dir/backup."*".$mode.tar.gz"; do
[ "$file" -nt "$ref" ] && ref="$file"
done
if [ "$delta" = "true" ]; then
delta_cmd=-N "'$ref'"
fi
backup_file="$dir/backup.$(date +%Y%m%d-%H%M%S).$mode.tar.gz"
case "$mode" in
config)
tar -cpzvf "$backup_file" $delta_cmd \
/etc \
/usr/local
;;
# still other modes here...
esac
我想将一个变量$delta\u cmd
传递给tar命令,以便它根据$delta
的值对自上次备份以来的所有文件或仅delta文件进行tar
如果$delta
设置为true,上述代码将创建一条错误消息,并且无法正确对delta文件进行tar。如何修复它
注意:脚本最好与POSIX兼容。您应该使用BASH数组来存储部分/完整的命令行:
#!/bin/bash
DIR=/home/sysop/backup
mode=main
delta=false
REF=$(ls -t "$DIR"/system.*.$mode.tar.gz "$DIR"/system.*.$mode-delta.tar.gz 2>/dev/null | head -n 1)
REF=$(readlink -f "$REF")
if [ "$delta" = true ]; then
delta_cmd=(-N "$REF")
delta_suffix=("-delta")
fi
target_file="$DIR/system.$(date +%Y%m%d-%H%M%S).$mode$delta_suffix.tar.gz"
tar -cpzvf "$target_file" "${delta_cmd[@]}" \
/etc \
/usr/local \
/var/log \
/var/spool \
/home/*/logs
我还建议避免在脚本中解析
ls
命令的输出。作为符合POSIX的方法,请考虑:
set -- # clear $@
if [ -f "$ref" ]; then
set -- "$@" -N "$ref" # add -N "$ref" to $@
fi
tar ... "$@" ... # expand $@ into command line
要将这一切放在上下文中,并保护主参数列表不被覆盖,可以如下所示:
#!/bin/sh
main() {
# if current shell supports "local", prevent variables from leaking
# ...some "POSIX" shells, such as ash, will be fine with this.
local dir mode delta target_file backup_file 2>&1 ||:
dir=$1
mode=$2
delta=$3
set -- # clear $@
for file in "$dir/backup."*".$mode.tar.gz"; do
[ "$file" -nt "$ref" ] && ref="$file"
done
if [ "$delta" = "true" ]; then
set -- "$@" -N "$ref"
fi
target_file="$dir/backup.$(date +%Y%m%d-%H%M%S).$mode.tar.gz"
case "$mode" in
config)
tar -cpzvf "$target_file" "$@" \
/etc \
/usr/local
;;
# still other modes here...
esac
}
main "$@"
由于语法错误,它似乎无法正常工作。我也尝试过使用bash,但是如果$delta=true,结果会像以前一样出错。这个脚本应该在bash上运行。您能告诉我使用BASH时的确切错误是什么吗?哦,您在数组赋值中的引号放错了位置。请注意,我有
delta_cmd=(-N“$REF”)
但您显示的是delta_cmd=(-N“$REF”)
您没有正确复制我的答案。我有“${delta\u cmd[@]}”
但你有$delta\u cmd
。你是对的。将$delta_cmd
替换为“${delta_cmd[@]}”
后,它现在可以工作了。可以使此POSIX兼容,但只能通过引入安全漏洞(使用eval
)或覆盖$
(这与delta_cmd
字符串的使用不兼容)--这意味着如果您不想覆盖该数组的脚本全局值,则将代码封装在函数中。有关为什么字符串变量(相对于数组)不能安全地用于存储参数列表或命令的说明,请参见BashFAQ#50。您能否进一步解释一下覆盖$@
的方式?添加了一个答案。顺便说一句,这段代码中有相当多的内容可以被删减以创建一个合适的MCVE(最小、完整、可验证的示例;请参见)——我非常犹豫是否在我的答案中包含诸如解析ls
之类的坏做法,但是,更好的实践替换该代码也有点超出了当前问题的范围……也就是说,请参阅BashFAQ#3,以获取有关在目录中查找最新或最旧文件的最佳实践方法的指导。(我们在StackOverflow知识库中也找到了关于使用GNU find高效地查找最新或最新的N个文件的答案,这与ls
不同)。谢谢。我减少了有问题的脚本。谢谢。这很有帮助。如果我们将代码片段放在子shell中的函数中,例如backup()(…)
,它不会覆盖$@是吗?正确,它只会覆盖该函数的$@
。实际上,您不需要backup()(…)
,backup(){…}
就足够了,因为函数位于不同的堆栈框架中,因此有自己的参数列表。请注意,原始帖子中有一个错误已被更正:目标_文件
应该是备份_文件
。