Remove an element from a Bash array

2020-01-24 06:58发布

I need to remove an element from an array in bash shell. Generally I'd simply do:

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?

20条回答
成全新的幸福
2楼-- · 2020-01-24 07:15

POSIX shell script does not have arrays.

So most probably you are using a specific dialect such as bash, korn shells or zsh.

Therefore, your question as of now cannot be answered.

Maybe this works for you:

unset array[$delete]
查看更多
女痞
3楼-- · 2020-01-24 07:16

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.

delete_ary_elmt() {
  local word=$1      # the element to search for & delete
  local aryref="$2[@]" # a necessary step since '${!$2[@]}' 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 "$2[$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

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.

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".

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).

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.

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).

查看更多
手持菜刀,她持情操
4楼-- · 2020-01-24 07:16

To expand on the above answers, the following can be used to remove multiple elements from an array, without partial matching:

ARRAY=(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

This will result in an array containing: (two onetwo three threefour "one six")

查看更多
冷血范
5楼-- · 2020-01-24 07:23

Using 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.

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]}"

Output is

aa cc dd ee
1st val is , 2nd val is cc
aa cc dd ee
1st val is cc, 2nd val is dd

Using :<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.

declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
arr2=("${arr[@]:1}")
echo "${arr2[@]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"

Output is

bb cc dd ee
1st val is cc, 2nd val is dd
查看更多
Emotional °昔
6楼-- · 2020-01-24 07:24

The following works as you would like in bash and 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.

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.

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
查看更多
迷人小祖宗
7楼-- · 2020-01-24 07:25

What I do is:

array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"

BAM, that item is removed.

查看更多
登录 后发表回答