首页 > 解决方案 > Bash函数获取任意数组的键,而不使用eval

问题描述

我写了一个函数来获取任意数组的键。

它按预期工作,但正在使用 evil eval

如果不使用,您将如何重写它eval

#!/usr/bin/env bash
# shellcheck disable=2034

# Return indexes of the array name
# @Params:
# $1: Name of the array
# @Output:
# >&1: Newline delimited list of indexes
function get_keys() {
  eval echo "\${!$1[@]}" | tr ' ' $'\n'
}

# Testing the get_keys function

# A numerical indexed array
declare -a a=([5]="a" [8]="b" [10]="c" [15]="d")
printf $'Standard array a:\nIndexes\tValues\n'
while read -r k; do
  printf $'%q\t%q\n' "$k" "${a[$k]}"
done < <(get_keys a)
echo

# An associative array
declare -A b=(["foo"]="hello" ["bar"]="world")
printf $'Associative array b:\nKeys\tValues\n'
while read -r k; do
  printf $'%q\t%q\n' "$k" "${b[$k]}"
done < <(get_keys b)
echo

输出:

Standard array a:
Indexes Values
5       a
8       b
10      c
15      d

Associative array b:
Keys    Values
foo     hello
bar     world

标签: arraysbasheval

解决方案


允许从函数参数进行间接引用的技巧是将变量声明为带有开关的nameref类型-n

可以使用-n选项为声明本地内置命令为变量分配nameref属性... nameref 通常在 shell 函数中用于引用其名称作为参数传递给函数的变量。例如,如果将变量名作为第一个参数传递给 shell 函数,则运行

          declare -n ref=$1

在函数内部创建一个 nameref 变量 ref,其值是作为第一个参数传递的变量名。

重要的 !

nameref变量类型需要 Bash 版本 ≥ 4.3 。

get_keys函数可以这样重写,而无需eval

# Return indexes of the array name
# @Params:
# $1: Name of the array
# @Output:
# >&1: Null delimited list of indexes
function get_keys() {
  local -n ref_arr="$1" # nameref of the array name argument
  printf '%s\0' "${!ref_arr[@]}" # null delimited for arbitrary keys
}

请注意,为了与可能包含控制字符的任意键兼容,该列表以空分隔符返回。在读取函数的输出时必须考虑它。

所以这里是一个完整的实现和测试get_keys以及配套的实用功能get_first_keyget_last_key并且get_first_last_keys

#!/usr/bin/env bash

# Return indexes of the array name
# @Params:
# $1: Name of the array
# @Output:
# >&1: Null delimited list of indexes
function get_keys() {
  local -n ref_arr="$1" # nameref of the array name argument
  printf '%s\0' "${!ref_arr[@]}"
}

# Return the first index of the array name
# @Params:
# $1: Name of the array
# @Output:
# >&1: the first index of the array
function get_first_key() {
  local -- first_key
  IFS= read -r -d '' first_key < <(get_keys "$1")
  printf '%s' "$first_key"
}

# Return the last index of the array name
# @Params:
# $1: Name of the array
# @Output:
# >&1: the last index of the array
function get_last_key() {
  local -- key last_key
  while IFS= read -r -d '' key && [ -n "$key" ]; do
    last_key="$key"
  done < <(get_keys "$1") # read keys until last one
  printf '%s' "$last_key"
}

# Return the first and the last indexes of the array name
# @Params:
# $1: Name of the array
# @Output:
# >&1: the first and last indexes of the array
function get_first_last_keys() {
  local -- key first_key last_key IFS=
  {
    read -r -d '' first_key # read the first key
    last_key="$first_key"   # in case there is only one key
    while IFS= read -r -d '' key && [ -n "$key" ]; do
      last_key="$key" # we'v read a new last key
    done
  } < <(get_keys "$1")
  printf '%s\0%s\0' "$first_key" "$last_key"
}

# Testing the get_keys function

# A numerical indexed array
declare -a a=([5]="a" [8]="b" [10]="c" [15]="d")
printf $"Standard array %s:\\n\\n" 'a'
typeset -p a
echo
printf '%-7s %-8s\n' $"Indexes" $"Values"
echo '----------------'

declare -i i # Array index as integer
# Iterate all array indexes returned by get_keys
while IFS= read -r -d '' i; do
  printf '%7d %-8s\n' "$i" "${a[$i]}"
done < <(get_keys a)
echo

# An associative array
unset b
declare -A b=(
  [$'\7']="First"
  [$'foo\nbar']="hello"
  ["bar baz"]="world"
  [";ls -l"]="command"
  ["No more!"]="Last one"
)
printf $"Associative array %s:\\n\\n" 'b'
typeset -p b
echo
printf '%-13s %-8s\n' $"Keys" $"Values"
echo '----------------------'
declare -- k # Array key
# Iterate all array keys returned by get_keys
while IFS= read -r -d '' k; do
  printf '%-13q %-8s\n' "$k" "${b[$k]}"
done < <(get_keys b)
echo
printf $"First key: %q\\n" "$(get_first_key b)"
printf $"Last key: %q\\n" "$(get_last_key b)"
declare -- first_key last_key
{
  IFS= read -r -d '' first_key
  IFS= read -r -d '' last_key
} < <(get_first_last_keys b)
printf $"First value: %s\\nLast value: %s\\n" "${b[$first_key]}" "${b[$last_key]}"

输出:

Standard array a:

declare -a a=([5]="a" [8]="b" [10]="c" [15]="d")

Indexes Values  
----------------
      5 a       
      8 b       
     10 c       
     15 d       

Associative array b:

declare -A b=(["No more!"]="Last one" [$'\a']="First" ["bar baz"]="world" [$'foo\nbar']="hello" [";ls -l"]="command" )

Keys          Values  
----------------------
No\ more\!    Last one
$'\a'         First   
bar\ baz      world   
$'foo\nbar'   hello   
\;ls\ -l      command 

First key: No\ more\!
Last key: \;ls\ -l
First value: Last one
Last value: command

推荐阅读