将数组动态分配给变量的值。评估?

将数组动态分配给变量的值。评估?

我正在使用 Bash 4。

我正在尝试用 Bash 进行一些实验。我想动态地将数组分配给变量的值。如果你阅读下面的代码,会更容易理解。

myFunc =RESULT_PREF 'bar' 'baz'

function myFunc() {
    local params
    parser params "$@"
}


function parser() {
    if [ '=' = "${2::1}" ]; then
        local name="${1}"
        shift 2
        local param_array=( "$@" )

        # In PHP, I'd do:
        # $$name = $param_array

        # The ideal outcome:
        # $params should contain the values 'bar' and 'baz'.
    fi
}

我该如何做我在代码注释中提到的事情?我似乎无法使用 eval 来做到这一点。即使我可以,我也读过很多关于 eval 的不好的东西。

有什么建议吗?

答案1

在 bash 4.3+ 中,有一种定义变量引用的方法,您可以在以下位置看到help declare

-n:使 NAME 成为对其值命名的变量的引用

因此,像这样的脚本输出一行a

n(){ declare -n g="$1"; g=a; }
n b; printf '%s\n' "$b";

所以你应该这样做parser

parser() {
    [ "$1" ] || ! echo 'no name given' >&2 || return 42
    if [[ "$2" == =* ]]; then
        declare -n name="$1"
        shift 2
        name=("$@")
    fi
}

使用eval确实有效,但如果过早扩展 rhs 可能会不安全:

shopt -e extglob # bash >= 3
[[ "$1" == [[:alpha:]_]*([[:word:]]) ]] || return 42 # invalid varname
# mikeserv has another solution for filtering, basically expanding
# the inverse of the expression to basic, portable globs
# Do this:
eval "$1"'=( "$@" )'
eval "$foo=\$bar"
# But not:
eval "$1=(\"$@\")"
eval "$foo=$bar"

我想那时你就会知道该怎么办eval。但只是为了让您更有可能接受我的答案,让我完整地写下:

shopt -s extglob
parser() {
    [[ "$1" == [[:alpha:]_]*([[:word:]]) ]] || ! echo 'bad varname' >&2 || return 42
    if [[ "$2" == =* ]]; then
        name="$1"
        shift 2
        eval "$name=(\"\$@\")"
    fi
}

答案2

myFunc(){
    parse "$1" &&                          #parse() is a test
    eval "  shift                          #$1 is a valid, eval-safe name
            local name$1 ${1#=}="'("$@")'  #$1 is expanded before shift
}

parse()
    case ${1#=} in
    ("$1"|""|[0-9]*|*[!_[:alnum:]]*) ! :   #return false for all invalid names
    esac

它也可以在不eval给出另外一两个执行步骤的情况下工作:

myFunc(){
    local -a "name$1[@]" "${1#=}"='("${@:2}")' &&
    [ -z "${1%=*}" ] && printf %s\\n "${!name}"
}

在这种情况下,我只允许local内置函数立即执行所有验证和分配。基本上如果local命令能分配"${1#=}"=("${@#"$1"}")成功 "${1%=*}"为 null 则您知道分配已成功进行。任何语法错误(例如错误的 shell 名称)都会在分配return期间自动失败并出现错误local,并且接下来的简单验证[测试]是确保您不会意外执行的所有必要操作local name=morename=("${@#"$1"}")

自然的结果是,当一个坏名字传入的=$1shell 会自动为您打印出一条有意义的错误消息到 stderr,并自动执行所有错误处理,无需大惊小怪。

像这样:

myFunc =goodname 1 2 3 'and some     ;more' &&
myFunc =bad-name 1 2 3 'and some     ;more'

1
2
3
还有一些;更多
bash:本地:`bad-name=("${@:2}")':不是有效的标识符

请注意,做functionname(){list;}几乎肯定不是你想做的。

如果您打算本地化函数的陷阱和类似的内容,您需要functionname{list;}

如果您打算local与当前 shell 共享除您定义的变量之外的所有状态,那么functionname(){list;}或者name()list是等效的,因为function关键字被 shell 忽略(除了一些实现它的 shell 倾向于解析以下内容list如果它没有包含在花括号中,则错误)什么时候name接下来是().

相关内容