3
#!/bin/bash

tank=(one two three)
x=two

unset tank[${x}]
echo ${tank[*]}

I want to remove x from the array but somehow it removes the first element of the array. How can I fix that?

3 Answers3

4

You have an indexed array, so the values in the [...] are treated as arithmetic expressions to generate an integer index. Strings in such an expressions are assumed to be parameter names, with undefined parameters evaluating to zero. Since two is undefined, your attempt is evaluated as

unset tank[${x}] -> unset tank[two] -> unset tank[0]

To safely remove an item from an array, you'll need to walk through the array, copying non-matching items to a new array, then assigning the new array back to the old name. This protects against splitting up array elements that might contain whitespace.

x=two
new_tank=()
for i in "${tank[@]}"; do
    if [[ $i != $x ]]; then
        new_tank+=("$i")
    fi
done
tank=( "${new_tank[@]}" )

More succinctly, as pointed out by gniourf_gniourf:

for i in "${!tank[@]}"; do
    [[ ${tank[i]} = $x ]] && unset tank[i]
done

Depending on what your application is, you may want to consider an associative array instead.

declare -A tank
tank=([one]=1 [two]=2 [three]=3)   # Using the keys as the actually elements
x=two
unset tank[$x]
# Prove that two is really gone, with no hole left behind.
for i in "${!tank[@]}"; do
   echo "$i"
done
chepner
  • 497,756
  • 71
  • 530
  • 681
  • You could also do without an auxilliary array: `for i in "${!tank[@]}"; do [[ ${tank[i]} = $x ]] && unset tank[i]; done; tank=( "${tank[@]}" )` – gniourf_gniourf Nov 16 '13 at 15:48
  • Ah, good point. I'll add that. – chepner Nov 16 '13 at 15:51
  • Note that `[[ $i != $x ]]` will treat the content of $x as a glob pattern, not a literal string. So if you try to remove `*` from the array, `*` will match everything and it'll remove all elements from the array. If you want to just remove exact matches rather than pattern matches, you need to double-quote `$x` (i.e. `[[ $i != "$x" ]]`). Actually, I tend to recommend double-quoting all variable references unless there's a specific reason not to (i.e. `[[ "$i" != "$x" ]]`); it's easier and safer than trying to remember where it matters and where it doesn't – Gordon Davisson Nov 16 '13 at 19:30
1

Since there is no way to match elements exactly, as with the regex ^two$, the only way I see is to rebuild a new array, excluding the unwanted element using test or [[ to perform exact matching:

tank=( first two twot twoot twooot )
unset work
x=two
for i in ${tank[@]}; do [[ $i = $x ]] || work+=($i); done
tank=$work
janos
  • 120,954
  • 29
  • 226
  • 236
0

EDIT user2997549 beware: altought the solution seems to works in this case, it is in general wrong. The following command executes a pattern replacement for all elements in the array, which actually works in your example only because you completely control the contents of the array. PLease accept chepner answer which is more general and addresses other issues, so I can then delete my answer.

To completely remove the element by value (not by index), you want a new array like in:

newtank=( ${tank[@]/two/} )

if you just unset an index, the hole will still be in the array; or if you can get rid of the old array

tank=( ${tank[@]/${x}/} )
guido
  • 18,864
  • 6
  • 70
  • 95