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

时间:2016-08-01 16:33:31

标签: shell

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

backup.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_cmd传递给tar命令,以便它自上次备份以来只包含所有文件或delta文件,具体取决于$delta的值。

如果$delta设置为true,上面的代码会创建一条错误消息并且不会正确地对增量文件进行tar。如何解决?

P.S:脚本最好与POSIX兼容。

2 个答案:

答案 0 :(得分:1)

您应该使用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命令的输出。

答案 1 :(得分:1)

作为符合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 "$@"