Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove an element from a bash array without flattening the array

Tags:

bash

I would like to make a function that takes a bash array like this one:

a=("element zero" "element one" "element two")

and removes one element like "element one" and leaves a the array like this:

a=("element zero" "element two")

such that echo $a[1] will print out element two and not zero.

I've seen several attempts at this, but haven't found one that did it cleanly or without breaking elements that have spaces into multiple elements. Or just setting the element to be blank (i.e. not shifting the indexes of subsequent array elements).

like image 311
William Everett Avatar asked Oct 29 '25 15:10

William Everett


1 Answers

# initial state
a=( "first element" "second element" "third element" )

# to remove
unset a[0]

# to reindex, such that a[0] is the old a[1], rather than having the array
# start at a[1] with no a[0] entry at all
a=( "${a[@]}" )

# to print the array with its indexes, to check its state at any stage
declare -p a

...now, for a function, if you have bash 4.3, you can use namevars to do this without any eval at all:

remove() {
  local -n _arr=$1      # underscore-prefixed name to reduce collision likelihood
  local idx=$2
  unset _arr[$idx]      # remove the undesired item
  _arr=( "${_arr[@]}" ) # renumber the indexes
}

For older versions of bash, it's a bit stickier:

remove() {
  local cmd
  unset "$1[$2]"
  printf -v cmd '%q=( "${%q[@]}" )' "$1" "$1" && eval "$cmd"
}

The use of printf with %q format strings is a bit of paranoia -- it makes it harder for maliciously chosen values (in this case, variable names) to perform actions of their choice, as opposed to simply failing with no effect.


All that said -- it's better if you don't renumber your arrays. If you leave off the renumbering step, such that after deleting entry a[1] you simply have a sparse array with no content at that index (which is different from an empty string at that index -- bash "arrays" are actually stored as linked lists or hash tables [in the associative case], not as arrays at all, so sparse arrays are memory-efficient), the delete operation is much faster.

This doesn't break your ability to iterate over your arrays if you retrieve ask the array for its keys rather than supplying them externally, as in:

for key in "${!a[@]}"; do
  value="${a[$key]}"
  echo "Entry $key has value $value"
done
like image 74
Charles Duffy Avatar answered Oct 31 '25 04:10

Charles Duffy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!