附加到以bash函数参数传递的w/其名称的数组

时间:2021-10-12 21:17:41

I am writing a function that adds an element to the end of an array passed in parameter:

我正在编写一个函数,在参数中传递的数组的末尾添加一个元素:

#@function add_elem_to_array: add an element to an array 
#in:
#1 name of the array
#2 element to add

add_elem_to_array()
{
   elem=$1  
   array=$2        
   index=${#array[@]} #get the index where to insert 

   eval "$array[$index]=$elem" #!!!! The problem is here
}

Could you please help me to figure out the solution?

你能帮我想一下解决方案吗?

4 个解决方案

#1


3  

Assuming bash 4.3 or newer, thus having namevars (declare -n / local -n):

假设bash 4.3或更新,因此具有namevars(声明-n / local -n):

add_elem_to_array() {
  local elem=$1 array_name=$2
  local -n array=$array_name
  array+=( "$elem" )
}

Supporting bash 3.x (particularly including 3.2, the oldest version in widespread use as of this writing):

支持bash 3。x(特别包括3.2,这是本文广泛使用的最古老版本):

add_elem_to_array() {
  local elem=$1 array_name=$2
  local cmd
  printf -v cmd '%q+=( %q )' "$array_name" "$elem"
  eval "$cmd"
}

That said -- given array+=( "$value" ) as an available syntax, there's little need for a function for the purpose, is there?

也就是说,给定数组+=(“$value”)作为一种可用语法,几乎不需要为此目的使用函数,是吗?

#2


3  

I wouldn't use a function for this:

我不会用函数来表示

array+=("$elem")

appends an element.

附加一个元素。

If you really want to use a function and you have Bash 4.3 or newer, you can use a nameref:

如果你真的想使用一个函数,你有Bash 4.3或更新,你可以使用nameref:

add_elem_to_array () {
    local elem=$1
    local -n arr=$2
    arr+=("$elem")
}

#3


2  

Charles Duffy's answer works perfectly for Bash 4.3+, but there's no simple solution if you're using an older version of Bash (unless you wish to trifle with eval for some awful reason). However, it can indeed be done!

Charles Duffy的答案对于Bash 4.3+非常有效,但是如果您使用的是一个旧版本的Bash(除非您出于某些可怕的原因想要玩弄eval),那么没有简单的解决方案。然而,它确实可以做到!

Here's what I whipped up:

## Arguments:
## $1 - the element to append
## $2 - the name of the array
append_to_array () {
    local -ia 'keys=(-1 "${!'$2'[@]}")';
    local IFS='';
    read -r -d '' -n ${#1} "$2"[${keys[${#keys[@]}-1]}+1] <<< "$1";
}

 

 


Explanation:

Indirection can be tricky and took me forever to learn, but it's powerful and fun so I figured I'd explain how everything fits together.

间接沟通可能很棘手,我花了很长时间才学会,但它很强大,也很有趣,所以我想我应该解释一下所有的东西是如何组合在一起的。

Let's use arr as the name of an array.
When you append elements to an array with something like arr+=(1) or arr+=("first element appended" "second element appended"), the indices(keys) of the elements in the array simply increment by 1 for each element. For example:

让我们使用arr作为数组的名称。当您将元素添加到具有arr+=(1)或arr+=(“第一个元素追加”“第二个元素追加”)的数组中时,数组中的元素的索引(键)对于每个元素仅增加1。例如:

$ declare -a arr=(A)
$ arr+=(B)
$ arr+=(C D)
$ declare -p arr
declare -a arr='([0]="A" [1]="B" [2]="C" [3]="D")'
$ echo ${#arr[@]}
4

You can see the size of the array is equal to the array's next available index, but this is not always the case. Continuing on:

可以看到,数组的大小等于数组的下一个可用索引,但情况并非总是如此。继续:

$ arr[7648]="E"
$ arr+=(F)
$ echo ${#arr[@]}
6
$ declare -p arr
declare -a arr='([0]="A" [1]="B" [2]="C" [3]="D" [7648]="E" [7649]="F")'

Line 1:
This is why in the first line of my function, I create an integer array, keys, from the indices of a ( ${!arr[@]} expands to the indices of arr. The last element in keys should be 1 less than the index we want to place the new element. However, if arr is unset or empty, ${!arr[@]} will expand to nothing, so I put -1 at the front of the keys to handle this.

第1行:这就是为什么在函数的第一行中,我从a的索引(${!arr[@]}扩展为arr的指标。键中的最后一个元素应该比要放置新元素的索引少1。但是,如果arr未设置或为空,则${!arr[@]}将会扩展为零,所以我将-1放在键的前面来处理这个。

Line 2:
Next up, we clear IFS (using local to avoid changing it outside of the function) to make sure any trailing or leading space characters in the appended element are preserved. Without clearing IFS, read and the here string operator <<< will strip leading and trailing space characters from "$1", which is undesirable.

第2行:接下来,我们清除IFS(使用local避免在函数外部进行更改),以确保在appended元素中保留任何尾随或领先的空格字符。如果没有清除IFS,请阅读下面的字符串操作符<<< <将会从“$1”中删除引导和尾随空格字符,这是不可取的。< p>

Line 3:
In the third line, we use read to copy the value from "$1" into the array referenced by $2. The -r prevents read from processing/interpreting special characters in "$1" and the -d '' option sets the delimiter to the null character to allow our elements to contain newlines (I will come back to the -n ${#1} option.).
${#keys[@]}-1 evaluates to the index of the last element in keys, so ${keys[${#keys[@]}-1]}+1 grabs the last element of keys and adds one to it, forming our desired index to place "$1".

第3行:在第三行,我们使用read将值从“$1”复制到$2引用的数组中。-r阻止在“$1”中处理/解释特殊字符,-d选项将分隔符设置为空字符,以允许我们的元素包含换行符(我将返回-n ${#1}选项)。${#keys[@]}-1计算键中最后一个元素的索引,因此${keys[${#keys[${}-1]}+1获取键的最后一个元素并向其添加一个元素,形成我们想要的索引以放置“$1”。

The read command can be used to write to elements in arrays, e.g. arr[2]="hi" could be replaced with read arr[2] <<< "hi", but read also works with indirect references to arrays, so we could also do nam=arr; read ${nam}[2] <<< "hi" or i=2; nam=arr; read ${nam}[$i] <<< "hi" and produce the same result. This is why read -r -d '' -n ${#1} ${2}[${keys[${#keys[@]}-1]}+1] <<< "$1" is able to append "$1" to the array referenced by $2.

read命令可用于对数组中的元素进行写入,例如,arr[2]=“hi”可以替换为read arr[2] << "hi", read也可以处理对数组的间接引用,所以我们也可以做nam=arr;读取${nam}[2] <<< "hi"或i=2;南= arr;读取${nam}[$i] << "hi"并产生相同的结果。这就是为什么读- r - d - n”{ 1 } { 2 }美元($ {键($ { #键[@]} 1]} + 1]< < < " $ 1 "能够" $ 1 "附加到该数组引用的2美元。

Finally, -n ${#1} is required for reasons unknown to me. When I first wrote the script, every appended element had a newline character appended to it. I do not know why this is, so hopefully someone else can share some insight. So I just worked around this problem by limiting the number of characters read to the number of characters in "$1".

最后,由于我不知道的原因,需要-n ${#1}。当我第一次编写脚本时,每个附加的元素都有一个换行字符。我不知道这是为什么,所以希望其他人能分享一些见解。因此,我只是通过限制“$1”中读取的字符数来解决这个问题。

 

 

Improved version that can append any number of elements and sanity-checks arguments:

改进的版本,可以添加任意数量的元素和安全检查参数:

## WARNING: THE ARGUMENTS ARE NOT IN THE SAME ORDER AS THE ABOVE FUNCTION
## $1 - the name of the array
## $2 - the first element to append
## $3-... - optional; can append any number of elements to the array
array_append () {
    [[ $# -gt 1 && $1 =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || { 2>&1 echo "invalid args"; return 1; };
    local -ia 'k=(-1 "${!'$1'[@]}")';
    local n="$1" IFS='';
    local -i j=k[${#k[@]}-1] i=1 r=$#;
    while ((i<r)); do
        shift;
        read -r -d '' -n ${#1} "$n"[i+++j] <<< "$1";
    done;
}

#4


0  

Your problem is actually in the line where you determine the index.

你的问题实际上在你决定索引的那条线上。

   eval index=\${#${array}[@]} #get the index where to insert

You need to expand the variable containing the name of the array using the eval, then expand the expression to get its length. Escaping the first dollar sign inhibits the expansion of the array length request until after the eval.

您需要使用eval展开包含数组名称的变量,然后展开表达式以获得其长度。转义第一个美元符号将抑制数组长度请求的扩展,直到eval结束。

The rest of the script appears to work as you intend. Here is a debugging version that I used so show what is happening:

脚本的其余部分似乎可以按照您的意愿工作。下面是我使用的调试版本,展示正在发生的事情:

add_elem_to_array()
{
   elem=$1
   array=$2
   eval index=\${#${array}[@]} #get the index where to insert

   echo "elem ='$elem'"
   echo "array='$array'"
   echo "index='$index'"

   eval "$array[$index]=$elem" #!!!! The problem is here
}

arr=(This is a test)
echo "arr = '${arr[@]}'"
add_elem_to_array "one" arr
echo "arr = '${arr[@]}'"
add_elem_to_array "two" arr
echo "arr = '${arr[@]}'"

#1


3  

Assuming bash 4.3 or newer, thus having namevars (declare -n / local -n):

假设bash 4.3或更新,因此具有namevars(声明-n / local -n):

add_elem_to_array() {
  local elem=$1 array_name=$2
  local -n array=$array_name
  array+=( "$elem" )
}

Supporting bash 3.x (particularly including 3.2, the oldest version in widespread use as of this writing):

支持bash 3。x(特别包括3.2,这是本文广泛使用的最古老版本):

add_elem_to_array() {
  local elem=$1 array_name=$2
  local cmd
  printf -v cmd '%q+=( %q )' "$array_name" "$elem"
  eval "$cmd"
}

That said -- given array+=( "$value" ) as an available syntax, there's little need for a function for the purpose, is there?

也就是说,给定数组+=(“$value”)作为一种可用语法,几乎不需要为此目的使用函数,是吗?

#2


3  

I wouldn't use a function for this:

我不会用函数来表示

array+=("$elem")

appends an element.

附加一个元素。

If you really want to use a function and you have Bash 4.3 or newer, you can use a nameref:

如果你真的想使用一个函数,你有Bash 4.3或更新,你可以使用nameref:

add_elem_to_array () {
    local elem=$1
    local -n arr=$2
    arr+=("$elem")
}

#3


2  

Charles Duffy's answer works perfectly for Bash 4.3+, but there's no simple solution if you're using an older version of Bash (unless you wish to trifle with eval for some awful reason). However, it can indeed be done!

Charles Duffy的答案对于Bash 4.3+非常有效,但是如果您使用的是一个旧版本的Bash(除非您出于某些可怕的原因想要玩弄eval),那么没有简单的解决方案。然而,它确实可以做到!

Here's what I whipped up:

## Arguments:
## $1 - the element to append
## $2 - the name of the array
append_to_array () {
    local -ia 'keys=(-1 "${!'$2'[@]}")';
    local IFS='';
    read -r -d '' -n ${#1} "$2"[${keys[${#keys[@]}-1]}+1] <<< "$1";
}

 

 


Explanation:

Indirection can be tricky and took me forever to learn, but it's powerful and fun so I figured I'd explain how everything fits together.

间接沟通可能很棘手,我花了很长时间才学会,但它很强大,也很有趣,所以我想我应该解释一下所有的东西是如何组合在一起的。

Let's use arr as the name of an array.
When you append elements to an array with something like arr+=(1) or arr+=("first element appended" "second element appended"), the indices(keys) of the elements in the array simply increment by 1 for each element. For example:

让我们使用arr作为数组的名称。当您将元素添加到具有arr+=(1)或arr+=(“第一个元素追加”“第二个元素追加”)的数组中时,数组中的元素的索引(键)对于每个元素仅增加1。例如:

$ declare -a arr=(A)
$ arr+=(B)
$ arr+=(C D)
$ declare -p arr
declare -a arr='([0]="A" [1]="B" [2]="C" [3]="D")'
$ echo ${#arr[@]}
4

You can see the size of the array is equal to the array's next available index, but this is not always the case. Continuing on:

可以看到,数组的大小等于数组的下一个可用索引,但情况并非总是如此。继续:

$ arr[7648]="E"
$ arr+=(F)
$ echo ${#arr[@]}
6
$ declare -p arr
declare -a arr='([0]="A" [1]="B" [2]="C" [3]="D" [7648]="E" [7649]="F")'

Line 1:
This is why in the first line of my function, I create an integer array, keys, from the indices of a ( ${!arr[@]} expands to the indices of arr. The last element in keys should be 1 less than the index we want to place the new element. However, if arr is unset or empty, ${!arr[@]} will expand to nothing, so I put -1 at the front of the keys to handle this.

第1行:这就是为什么在函数的第一行中,我从a的索引(${!arr[@]}扩展为arr的指标。键中的最后一个元素应该比要放置新元素的索引少1。但是,如果arr未设置或为空,则${!arr[@]}将会扩展为零,所以我将-1放在键的前面来处理这个。

Line 2:
Next up, we clear IFS (using local to avoid changing it outside of the function) to make sure any trailing or leading space characters in the appended element are preserved. Without clearing IFS, read and the here string operator <<< will strip leading and trailing space characters from "$1", which is undesirable.

第2行:接下来,我们清除IFS(使用local避免在函数外部进行更改),以确保在appended元素中保留任何尾随或领先的空格字符。如果没有清除IFS,请阅读下面的字符串操作符<<< <将会从“$1”中删除引导和尾随空格字符,这是不可取的。< p>

Line 3:
In the third line, we use read to copy the value from "$1" into the array referenced by $2. The -r prevents read from processing/interpreting special characters in "$1" and the -d '' option sets the delimiter to the null character to allow our elements to contain newlines (I will come back to the -n ${#1} option.).
${#keys[@]}-1 evaluates to the index of the last element in keys, so ${keys[${#keys[@]}-1]}+1 grabs the last element of keys and adds one to it, forming our desired index to place "$1".

第3行:在第三行,我们使用read将值从“$1”复制到$2引用的数组中。-r阻止在“$1”中处理/解释特殊字符,-d选项将分隔符设置为空字符,以允许我们的元素包含换行符(我将返回-n ${#1}选项)。${#keys[@]}-1计算键中最后一个元素的索引,因此${keys[${#keys[${}-1]}+1获取键的最后一个元素并向其添加一个元素,形成我们想要的索引以放置“$1”。

The read command can be used to write to elements in arrays, e.g. arr[2]="hi" could be replaced with read arr[2] <<< "hi", but read also works with indirect references to arrays, so we could also do nam=arr; read ${nam}[2] <<< "hi" or i=2; nam=arr; read ${nam}[$i] <<< "hi" and produce the same result. This is why read -r -d '' -n ${#1} ${2}[${keys[${#keys[@]}-1]}+1] <<< "$1" is able to append "$1" to the array referenced by $2.

read命令可用于对数组中的元素进行写入,例如,arr[2]=“hi”可以替换为read arr[2] << "hi", read也可以处理对数组的间接引用,所以我们也可以做nam=arr;读取${nam}[2] <<< "hi"或i=2;南= arr;读取${nam}[$i] << "hi"并产生相同的结果。这就是为什么读- r - d - n”{ 1 } { 2 }美元($ {键($ { #键[@]} 1]} + 1]< < < " $ 1 "能够" $ 1 "附加到该数组引用的2美元。

Finally, -n ${#1} is required for reasons unknown to me. When I first wrote the script, every appended element had a newline character appended to it. I do not know why this is, so hopefully someone else can share some insight. So I just worked around this problem by limiting the number of characters read to the number of characters in "$1".

最后,由于我不知道的原因,需要-n ${#1}。当我第一次编写脚本时,每个附加的元素都有一个换行字符。我不知道这是为什么,所以希望其他人能分享一些见解。因此,我只是通过限制“$1”中读取的字符数来解决这个问题。

 

 

Improved version that can append any number of elements and sanity-checks arguments:

改进的版本,可以添加任意数量的元素和安全检查参数:

## WARNING: THE ARGUMENTS ARE NOT IN THE SAME ORDER AS THE ABOVE FUNCTION
## $1 - the name of the array
## $2 - the first element to append
## $3-... - optional; can append any number of elements to the array
array_append () {
    [[ $# -gt 1 && $1 =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || { 2>&1 echo "invalid args"; return 1; };
    local -ia 'k=(-1 "${!'$1'[@]}")';
    local n="$1" IFS='';
    local -i j=k[${#k[@]}-1] i=1 r=$#;
    while ((i<r)); do
        shift;
        read -r -d '' -n ${#1} "$n"[i+++j] <<< "$1";
    done;
}

#4


0  

Your problem is actually in the line where you determine the index.

你的问题实际上在你决定索引的那条线上。

   eval index=\${#${array}[@]} #get the index where to insert

You need to expand the variable containing the name of the array using the eval, then expand the expression to get its length. Escaping the first dollar sign inhibits the expansion of the array length request until after the eval.

您需要使用eval展开包含数组名称的变量,然后展开表达式以获得其长度。转义第一个美元符号将抑制数组长度请求的扩展,直到eval结束。

The rest of the script appears to work as you intend. Here is a debugging version that I used so show what is happening:

脚本的其余部分似乎可以按照您的意愿工作。下面是我使用的调试版本,展示正在发生的事情:

add_elem_to_array()
{
   elem=$1
   array=$2
   eval index=\${#${array}[@]} #get the index where to insert

   echo "elem ='$elem'"
   echo "array='$array'"
   echo "index='$index'"

   eval "$array[$index]=$elem" #!!!! The problem is here
}

arr=(This is a test)
echo "arr = '${arr[@]}'"
add_elem_to_array "one" arr
echo "arr = '${arr[@]}'"
add_elem_to_array "two" arr
echo "arr = '${arr[@]}'"