如何将可变数量的参数传递给shell命令?

如何将可变数量的参数传递给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

我正在编写一个备份脚本,如下所示:

备份。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.$(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(){…}
就足够了,因为函数位于不同的堆栈框架中,因此有自己的参数列表。请注意,原始帖子中有一个错误已被更正:
目标_文件
应该是
备份_文件