【问题标题】:Update array passed by reference with BASH使用 BASH 更新通过引用传递的数组
【发布时间】:2015-06-10 23:23:05
【问题描述】:

我想编写一个函数,它接受一个数组变量名并更新内容。例如:

ARRAY1=("test 1" "test 2" "test 3")
toUpper ARRAY1

for arg in "${ARRAY1[@]}"; do
  echo "arg=$arg"
done

# output
arg=TEST 1
arg=TEST 2
arg=TEST 3

我做了一个粗略的尝试,这需要输入数组的副本。使用间接引用,我能够创建输入变量的副本。数组的副本用于获取元素的计数。如果有更好的方法,请告诉我。

function toUpper() {
  local ARRAY_NAME=$1
  local ARRAY_REF="$ARRAY_NAME[@]"
  # use an indirect reference to copy the array so we can get the count
  declare -a ARRAY=("${!ARRAY_REF}")

  local COUNT=${#ARRAY[@]}

  for ((i=0; i<$COUNT; i++)); do
    local VAL="${ARRAY[$i]}"
    VAL=$(echo $VAL | tr [:lower:] [:upper:])
    echo "ARRAY[$i]=\"$VAL\""
    eval "$ARRAY_NAME[$i]=\"$VAL\""
  done
}

ARRAY1=( "test" "test 1" "test 3" )

toUpper ARRAY1

echo
echo "Printing array contents"
for arg in "${ARRAY1[@]}"; do
  echo "arg=$arg"
done

【问题讨论】:

  • 你的 BASH 版本是多少?
  • 使用 BASH 4,但如果可能的话,如果解决方案适用于版本 3,那就太好了。
  • 顺便说一句,非导出的 shell 变量的全大写变量名是错误的形式。根据 POSIX 规范 (pubs.opengroup.org/onlinepubs/009695399/basedefs/…):“包含小写字母的环境变量名称空间是为应用程序保留的”;由于环境变量和局部变量共享一个命名空间,使用小写名称可以防止意外覆盖环境变量和内置变量。
  • 顺便说一句——最好避免使用function 关键字。它没有语义用途——function foo() { ...; } 与 bash 中的foo() { ...; } 具有完全相同的效果——但不必要地使代码与 POSIX sh 不兼容。

标签: arrays bash pass-by-reference indirection


【解决方案1】:

使用 BASH 4.3+ 你可以做到

arr=( "test" "test 1" "test 3" )
toUpper() { declare -n tmp="$1"; printf "%s\n" "${tmp[@]^^}"; }

toUpper arr
TEST
TEST 1
TEST 3

更新:反映原始数组的变化:

toUpper() {
   declare -n tmp="$1"; 
   for ((i=0; i<"${#tmp[@]}"; i++)); do
      tmp[i]="${tmp[i]^^}"
    done;
}

arr=( "test" "test 1" "test 3" )
toUpper arr
printf "%s\n" "${arr[@]}"
TEST
TEST 1
TEST 3

Update2:这是一种使它在没有eval旧 BASH(4 之前)版本中工作的方法:

upper() {
   len=$2
   for ((i=0; i<len; i++)); do
      elem="${1}[$i]"
      val=$(tr '[:lower:]' '[:upper:]' <<< "${!elem}")
      IFS= read -d '' -r "${1}[$i]" < <(printf '%s\0' "$val")
   done;
}

arr=( "test" "test 1" "test 3" )
upper arr ${#arr[@]}
printf "%s\n" "${arr[@]}"
TEST
TEST 1
TEST 3

【讨论】:

  • 不只是4+,而是4.3+;也就是说,当瞄准一个足够新的 bash 时,这当然是最好的选择。
  • 我相信是declare -n(参考变量)是在4.3中添加的
  • 如果不想对数据产生副作用(扩展反斜杠转义序列),我建议使用read -r
  • 引用[:lower:][:upper:] 也可能是合适的,以防止这些字符串在传递给tr 之前进行全局匹配。我注意到我自己也犯了同样的错误。
  • @anubhava,如果我们不想在字符串的第一个换行符处截断。 IFS 控制用于修剪单词的字符,以及从末尾修剪被视为空白的字符; -d 控制结束由单个调用 read 读取的内容的字符。比较IFS= read -r &lt;&lt;&lt;$'one\ntwo'; printf '%q\n' "$REPLY"IFS= read -r -d '' &lt;&lt;&lt;$'one\ntwo'; printf '%q\n' "${REPLY%$'\n'}"
【解决方案2】:

anubhava 的答案非常适合 bash 4.3 或更高版本。为了支持 bash 3,可以使用eval(非常谨慎,使用printf %q 生成的字符串)来替换namevars 的使用,以及tr 来替换大写的${foo^^} 扩展:

toUpper() {
  declare -a indexes
  local cmd idx item result
  printf -v cmd 'indexes=( "${!%q[@]}" )' "$1"; eval "$cmd"
  for idx in "${indexes[@]}"; do
     printf -v cmd 'item=${%q[%q]}' "$1" "$idx"; eval "$cmd"
     result=$(tr '[:lower:]' '[:upper:]' <<<"$item")
     printf -v cmd '%q[%q]=%q' "$1" "$idx" "$result"; eval "$cmd"
  done
}

【讨论】:

  • (值得注意的是,在较新版本的 bash 中,printf -v 可以直接分配给数组元素;但是,这是 4.2 的一项功能,在 3.x 中不可用——但如果有的话仍然会有所帮助不是相当运行 4.3)。
猜你喜欢
  • 2017-05-12
  • 2011-08-09
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
  • 2020-07-30
  • 2012-04-17
  • 2014-08-06
相关资源
最近更新 更多