将 $@ 分配给 shell 函数中的另一个变量

将 $@ 分配给 shell 函数中的另一个变量

我正在创建一个gc可以区分 git commit/checkout 的智能别名。

如果在不带任何参数或使用,参数gc的情况下调用,则运行 git commit。否则,运行 git checkout (如果有带有附加参数的标志)。如果调用 gc 的任何其他变体,我希望它抛出错误而不是做一些意想不到的事情。-a-mb

这是到目前为止我的 shell 函数。

gc() {
    args=$@
    commit=false
    checkout=false

    # Check flags
    while getopts ":am:b" opt; do
        case $opt in
            a|m)
                commit=true
                ;;
            b)
                checkout=true
                ;;
            \?)
                echo "Unknown flags passed."
                return 1
                ;;
        esac
    done

    shift "$((OPTIND-1))"

    # Check number of arguments
    if [[ "$#" == 0 ]]; then
        commit=true
    elif [[ "$#" == 1 ]]; then
        checkout=true
    else
        echo "Too many arguments"
        return 1
    fi

    # Finally run respective command
    if [[ $commit == true && $checkout == true ]]; then
        echo "Unable to ascertain which operation you would like to perform."
        return 1
    elif [[ $commit == true ]]; then
        git commit "$args"
    elif [[ $checkout == true ]]; then
        git checkout "$args"
    else
        echo "Undefined behavior"
        return 1
    fi
}

但是,这不能正常工作。经过一番实验后,我发现分配$@给另一个变量是根本原因,但我无法理解为什么以及到底出了什么问题。

另外,由于我是第一次编写 shell 函数,请突出显示我所犯的任何错误。

答案1

$@是一个数组,将其赋值给一个数组:

args=("$@")

然后将其用作数组:

elif [[ $commit == true ]]; then
    git commit "${args[@]}"
elif [[ $checkout == true ]]; then
    git checkout "${args[@]}"
else

当前代码中发生的情况是,所有单独的参数都存储为单个字符串。所以如果你打电话:

bc -a "foo bar"

被分配args为:

args='-a foo bar'

然后而不是执行:

git commit -a "foo bar"

你得到:

git commit '-a foo bar'

其他一些注意事项:

  • 在 zsh 中,您还可以使用"$args[@]""${(@)args}"代替"${args[@]}",但在 bash/ksh 中则不然。"$args"在 yash 中可以工作,但在 中,这与(与 的第一个字符连接的元素)zsh相同,在 ksh/bash 中与 相同。在 zsh 中,可以工作,但会删除空元素(如果有)。"$args[*]"$IFS"${args[0]}"$args
  • getopts在函数中使用时,您应该事先添加一些local OPTIND OPTARG(尤其是OPTIND或至少初始化/取消设置它)。 zshOPTIND在不模拟其他 shell 时会自动执行此操作,但 ksh/bash/yash 不会。您在函数中使用的其他变量也应该设置为本地变量。
  • 错误应该发送到 stderr (fd 2):echo>&2 Too many arguments等等。

答案2

由于另一个答案不是 POSIX,因此这里有一个替代方案。如果你想暂时粉碎位置参数,你可以这样做:

s1=$(printf '%s\n' "$@")

然后当您准备好恢复时,请执行以下操作:

IFS='
'
set -- $s1

请注意,这假设参数不包含换行符。如果这样做,则需要使用不同的分隔符。

旁注:如果您注意到,最后一行包含一个未加引号的变量。这是可以接受不带引号的变量的少数情况之一(在我看来,是唯一的情况):用户已明确设置IFS.用户本质上是说“是的,我知道我在做什么,请继续”。

相关内容