从 Bash 数组中删除一个元素
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16860877/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Remove an element from a Bash array
提问by Alex
I need to remove an element from an array in bash shell. Generally I'd simply do:
我需要从 bash shell 中的数组中删除一个元素。通常我会简单地做:
array=("${(@)array:#<element to remove>}")
Unfortunately the element I want to remove is a variable so I can't use the previous command. Down here an example:
不幸的是,我想删除的元素是一个变量,所以我不能使用上一个命令。下面是一个例子:
array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[@]/$delete} ) -> but clearly doesn't work because of {}
Any idea?
任何的想法?
回答by chepner
The following works as you would like in bash
and zsh
:
以下工作如您所愿在bash
和 中zsh
:
$ array=(pluto pippo)
$ delete=(pluto)
$ echo ${array[@]/$delete}
pippo
$ array=( "${array[@]/$delete}" ) #Quotes when working with strings
If need to delete more than one element:
如果需要删除多个元素:
...
$ delete=(pluto pippo)
for del in ${delete[@]}
do
array=("${array[@]/$del}") #Quotes when working with strings
done
Caveat
警告
This technique actually removes prefixes matching $delete
from the elements, not necessarily whole elements.
这种技术实际上$delete
从元素中删除了前缀匹配,不一定是整个元素。
Update
更新
To really remove an exact item, you need to walk through the array, comparing the target to each element, and using unset
to delete an exact match.
要真正删除精确项,您需要遍历数组,将目标与每个元素进行比较,然后使用unset
删除精确匹配项。
array=(pluto pippo bob)
delete=(pippo)
for target in "${delete[@]}"; do
for i in "${!array[@]}"; do
if [[ ${array[i]} = $target ]]; then
unset 'array[i]'
fi
done
done
Note that if you do this, and one or more elements is removed, the indices will no longer be a continuous sequence of integers.
请注意,如果您这样做,并且删除了一个或多个元素,则索引将不再是连续的整数序列。
$ declare -p array
declare -a array=([0]="pluto" [2]="bob")
The simple fact is, arrays were not designed for use as mutable data structures. They are primarily used for storing lists of items in a single variable without needing to waste a character as a delimiter (e.g., to store a list of strings which can contain whitespace).
一个简单的事实是,数组不是为用作可变数据结构而设计的。它们主要用于在单个变量中存储项目列表,而无需浪费一个字符作为分隔符(例如,存储可以包含空格的字符串列表)。
If gaps are a problem, then you need to rebuild the array to fill the gaps:
如果间隙是一个问题,那么您需要重建阵列以填补间隙:
for i in "${!array[@]}"; do
new_array+=( "${array[i]}" )
done
array=("${new_array[@]}")
unset new_array
回答by Steve Kehlet
You could build up a new array without the undesired element, then assign it back to the old array. This works in bash
:
您可以构建一个没有不需要元素的新数组,然后将其分配回旧数组。这适用于bash
:
array=(pluto pippo)
new_array=()
for value in "${array[@]}"
do
[[ $value != pluto ]] && new_array+=($value)
done
array=("${new_array[@]}")
unset new_array
This yields:
这产生:
echo "${array[@]}"
pippo
回答by signull
This is the most direct way to unset a value if you know it's position.
如果您知道它的位置,这是取消设置值的最直接方法。
$ array=(one two three)
$ echo ${#array[@]}
3
$ unset 'array[1]'
$ echo ${array[@]}
one three
$ echo ${#array[@]}
2
回答by Niklas Holm
Here's a one-line solution with mapfile:
这是一个带有 mapfile 的单行解决方案:
$ mapfile -d $'$ arr=("Adam" "Bob" "Claire"$'\n'"Smith" "David" "Eve" "Fred")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 6 Contents: Adam Bob Claire
Smith David Eve Fred
$ mapfile -d $'delete_ary_elmt() {
local word= # the element to search for & delete
local aryref="[@]" # a necessary step since '${![@]}' is a syntax error
local arycopy=("${!aryref}") # create a copy of the input array
local status=1
for (( i = ${#arycopy[@]} - 1; i >= 0; i-- )); do # iterate over indices backwards
elmt=${arycopy[$i]}
[[ $elmt == $word ]] && unset "[$i]" && status=0 # unset matching elmts in orig. ary
done
return $status # return 0 if something was deleted; 1 if not
}
array=(a 0 0 b 0 0 0 c 0 d e 0 0 0)
delete_ary_elmt 0 array
for e in "${array[@]}"; do
echo "$e"
done
# prints "a" "b" "c" "d" in lines
' -t arr < <(printf '%sARRAY=(one two onetwo three four threefour "one six")
TO_REMOVE=(one four)
TEMP_ARRAY=()
for pkg in "${ARRAY[@]}"; do
for remove in "${TO_REMOVE[@]}"; do
KEEP=true
if [[ ${pkg} == ${remove} ]]; then
KEEP=false
break
fi
done
if ${KEEP}; then
TEMP_ARRAY+=(${pkg})
fi
done
ARRAY=("${TEMP_ARRAY[@]}")
unset TEMP_ARRAY
' "${arr[@]}" | grep -Pzv "^Claire\nSmith$")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 5 Contents: Adam Bob David Eve Fred
' -t arr < <(printf '%s N K New(seconds) Current(seconds) Speedup
1000 10 0.005 0.033 6X
10000 10 0.070 0.348 5X
10000 20 0.070 0.656 9X
10000 1 0.043 0.050 -7%
' "${arr[@]}" | grep -Pzv "<regexp>")
Example:
例子:
declare -A delk
for del in "${delete[@]}" ; do delk[$del]=1 ; done
# Tag items to remove, based on
for k in "${!array[@]}" ; do
[ "${delk[${array[$k]}]-}" ] && unset 'array[k]'
done
# Compaction
array=("${array[@]}")
This method allows for great flexibility by modifying/exchanging the grep command and doesn't leave any empty strings in the array.
此方法通过修改/交换 grep 命令提供了极大的灵活性,并且不会在数组中留下任何空字符串。
回答by S.V.P.
Here's a (probably very bash-specific) little function involving bash variable indirection and unset
; it's a general solution that does not involve text substitution or discarding empty elements and has no problems with quoting/whitespace etc.
这是一个(可能非常特定于 bash 的)小函数,涉及 bash 变量间接和unset
; 这是一个通用的解决方案,不涉及文本替换或丢弃空元素,并且没有引用/空格等问题。
for target in "${delete[@]}"; do
for i in "${!array[@]}"; do
if [[ ${array[i]} = $target ]]; then
unset 'array[i]'
fi
done
done
array=("${array[@]}")
Use it like delete_ary_elmt ELEMENT ARRAYNAME
without any $
sigil. Switch the == $word
for == $word*
for prefix matches; use ${elmt,,} == ${word,,}
for case-insensitive matches; etc., whatever bash [[
supports.
像delete_ary_elmt ELEMENT ARRAYNAME
没有任何$
印记一样使用它。切换== $word
for == $word*
for 前缀匹配;使用${elmt,,} == ${word,,}
为不区分大小写匹配; 等等,无论 bash[[
支持什么。
It works by determining the indices of the input array and iterating over them backwards (so deleting elements doesn't screw up iteration order). To get the indices you need to access the input array by name, which can be done via bash variable indirection x=1; varname=x; echo ${!varname} # prints "1"
.
它的工作原理是确定输入数组的索引并向后迭代它们(因此删除元素不会破坏迭代顺序)。要获取索引,您需要按名称访问输入数组,这可以通过 bash 变量间接完成x=1; varname=x; echo ${!varname} # prints "1"
。
You can't access arrays by name like aryname=a; echo "${$aryname[@]}
, this gives you an error. You can't do aryname=a; echo "${!aryname[@]}"
, this gives you the indices of the variable aryname
(although it is not an array). What DOES work is aryref="a[@]"; echo "${!aryref}"
, which will print the elements of the array a
, preserving shell-word quoting and whitespace exactly like echo "${a[@]}"
. But this only works for printing the elements of an array, not for printing its length or indices (aryref="!a[@]"
or aryref="#a[@]"
or "${!!aryref}"
or "${#!aryref}"
, they all fail).
您无法按名称访问数组,例如aryname=a; echo "${$aryname[@]}
,这会给您带来错误。您不能这样做aryname=a; echo "${!aryname[@]}"
,这为您提供了变量的索引aryname
(尽管它不是数组)。什么工作是aryref="a[@]"; echo "${!aryref}"
,它将打印数组的元素,a
完全像echo "${a[@]}"
. 但这仅适用于打印数组的元素,不适用于打印其长度或索引(aryref="!a[@]"
oraryref="#a[@]"
或"${!!aryref}"
or "${#!aryref}"
,它们都失败)。
So I copy the original array by its name via bash indirection and get the indices from the copy. To iterate over the indices in reverse I use a C-style for loop. I could also do it by accessing the indices via ${!arycopy[@]}
and reversing them with tac
, which is a cat
that turns around the input line order.
因此,我通过 bash 间接方式按名称复制原始数组,并从副本中获取索引。为了反向迭代索引,我使用了 C 风格的 for 循环。我也可以通过访问索引 via${!arycopy[@]}
并用 反转它们来实现tac
,这是一个cat
可以改变输入行顺序的。
A function solution without variable indirection would probably have to involve eval
, which may or may not be safe to use in that situation (I can't tell).
没有变量间接性的函数解决方案可能必须涉及eval
,在那种情况下使用它可能安全也可能不安全(我不知道)。
回答by Dylan
To expand on the above answers, the following can be used to remove multiple elements from an array, without partial matching:
为了扩展上述答案,以下内容可用于从数组中删除多个元素,而无需部分匹配:
declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
unset 'arr[1]'
declare -a arr2=()
i=0
for element in "${arr[@]}"
do
arr2[$i]=$element
((++i))
done
echo "${arr[@]}"
echo "1st val is ${arr[1]}, 2nd val is ${arr[2]}"
echo "${arr2[@]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"
This will result in an array containing: (two onetwo three threefour "one six")
这将导致一个包含以下内容的数组:(二一二三三四“一六”)
回答by dash-o
This answer is specific to the case of deleting multiple values from large arrays, where performance is important.
此答案特定于从大型数组中删除多个值的情况,其中性能很重要。
The most voted solutions are (1) pattern substitution on an array, or (2) iterating over the array elements. The first is fast, but can only deal with elements that have distinct prefix, the second has O(n*k), n=array size, k=elements to remove. Associative array are relative new feature, and might not have been common when the question was originally posted.
投票最多的解决方案是 (1) 数组上的模式替换,或 (2) 迭代数组元素。第一个速度很快,但只能处理具有不同前缀的元素,第二个有 O(n*k),n=数组大小,k=要删除的元素。关联数组是相对较新的功能,在最初发布问题时可能并不常见。
For the exact match case, with large n and k, possible to improve performance from O(nk) to O(n+klog(k)). In practice, O(n) assuming k much lower than n. Most of the speed up is based on using associative array to identify items to be removed.
对于精确匹配的情况,n 和 k 较大,可以将性能从 O(n k) 提高到 O(n+klog(k))。实际上,O(n) 假设 k 远低于 n。大多数加速是基于使用关联数组来识别要删除的项目。
Performance (n-array size, k-values to delete). Performance measure seconds of user time
性能(n 数组大小,要删除的 k 值)。用户时间的性能度量秒
aa cc dd ee
1st val is , 2nd val is cc
aa cc dd ee
1st val is cc, 2nd val is dd
As expected, the current
solution is linear to N*K, and the fast
solution is practically linear to K, with much lower constant. The fast
solution is slightly slower vs the current
solution when k=1, due to additional setup.
正如预期的那样,current
解与 N*K 呈线性关系,而fast
解实际上与 K 呈线性关系,但常数要低得多。该fast
溶液略慢VS的是current
溶液当k = 1时,由于额外设置。
The 'Fast' solution: array=list of input, delete=list of values to remove.
“快速”解决方案:数组=输入列表,删除=要删除的值列表。
declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
arr2=("${arr[@]:1}")
echo "${arr2[@]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"
Benchmarked against current
solution, from the most-voted answer.
根据current
投票最多的答案对解决方案进行基准测试。
bb cc dd ee
1st val is cc, 2nd val is dd
回答by Konstantin Gredeskoul
If anyone finds themselves in a position where they need to remember set -e or set -x values and be able to restore them, please check out this gist which uses the first array deletion solution to manage it's own stack:
如果有人发现自己需要记住 set -e 或 set -x 值并能够恢复它们,请查看这个使用第一个数组删除解决方案来管理自己堆栈的要点:
https://gist.github.com/kigster/94799325e39d2a227ef89676eed44cc6
https://gist.github.com/kigster/94799325e39d2a227ef89676eed44cc6
回答by rashok
Using unset
使用 unset
To remove an element at particular index, we can use unset
and then do copy to another array. Only just unset
is not required in this case. Because unset
does not remove the element it just sets null string to the particular index in array.
要删除特定索引处的元素,我们可以使用unset
然后复制到另一个数组。unset
在这种情况下,仅不需要。因为unset
不删除元素,它只是将空字符串设置为数组中的特定索引。
unset 'array[0]'
Output is
输出是
unset 'array[-1]'
Using :<idx>
使用 :<idx>
We can remove some set of elements using :<idx>
also. For example if we want to remove 1st element we can use :1
as mentioned below.
我们也可以使用删除一些元素集:<idx>
。例如,如果我们想删除第一个元素,我们可以使用:1
如下所述。
Output is
输出是
##代码##回答by consideRatio
Partial answer only
仅部分回答
To delete the first item in the array
删除数组中的第一项
##代码##To delete the last item in the array
删除数组中的最后一项
##代码##