11
1  #!/bin/bash
2  # query2.sh
3
4  numbers=(53 8 12 9 784 69 8 7 1)
5  i=4
6
7  echo ${numbers[@]} # <--- this echoes "53 8 12 9 784 69 8 7 1" to stdout.
8  echo ${numbers[i]} # <--- this echoes "784" to stdout.
9
10 unset numbers[i]
11
12 echo ${numbers[@]} # <--- this echoes "53 8 12 9 69 8 7 1" to stdout.
13 echo ${numbers[i]} # <--- stdout is blank.

Why, in line 13, is the stdout blank, considering that the array seems to have been updated judging by line 12's stdout?

And therefore, what should I do to get the intended answer, "69"?

21

unset removes an element. It doesn't renumber the remaining elements.

We can use declare -p to see exactly what happens to numbers:

$ unset "numbers[i]"
$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [5]="69" [6]="8" [7]="7" [8]="1")

Observe the numbers no longer has an element 4.

Another example

Observe:

$ a=()
$ a[1]="element 1"
$ a[22]="element 22"
$ declare -p a
declare -a a=([1]="element 1" [22]="element 22")

Array a has no elements 2 through 21. Bash does not require that array indices be consecutive.

Suggested method to force a renumbering of the indices

Let's start with the numbers array with the missing element 4:

$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [5]="69" [6]="8" [7]="7" [8]="1")

If we would like the indices to change, then:

$ numbers=("${numbers[@]}")
$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [4]="69" [5]="8" [6]="7" [7]="1")

There is now an element number 4 and it has value 69.

Alternate method to remove an element & renumber array in one step

Again, let's define numbers:

$ numbers=(53 8 12 9 784 69 8 7 1)

As suggested by Toby Speight in the comments, a method to remove the fourth element and renumber the remaining elements all in one step:

$ numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")
$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [4]="69" [5]="8" [6]="7" [7]="1")

As you can see, the fourth element was removed and all remaining elements were renumbered.

${numbers[@]:0:4} slices array numbers: it takes the first four elements starting with element 0.

Similarly, ${numbers[@]:5} slice array numbers: it takes all elements starting with element 5 and continuing to the end of the array.

Obtaining the indices of an array

The values of an array can be obtained with ${a[@]}. To find the indices (or keys) that correspond to those values, use ${!a[@]}.

For example, consider again our array numbers with the missing element 4:

$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [5]="69" [6]="8" [7]="7" [8]="1")

To see which indices are assigned:

$ echo "${!numbers[@]}"
0 1 2 3 5 6 7 8

Again, 4 is missing from the list of indices.

Documentation

From man bash:

The unset builtin is used to destroy arrays. unset name[subscript] destroys the array element at index subscript. Negative subscripts to indexed arrays are interpreted as described above. Care must be taken to avoid unwanted side effects caused by pathname expansion. unset name, where name is an array, or unset name[subscript], where subscript is * or @, removes the entire array.

  • 1
    Note that shell array syntax is really just a way to make it easy to deal with similarly named variables. There is no array itself; in fact, after you write a=(), the variable a is still undefined until you actually assign to one of its indices. – chepner Sep 19 '17 at 0:12
  • @John1024: Thank you for this answer. Could you possibly expand it to include a suggested answer for achieving the intended result? – Anthony Webber Sep 19 '17 at 0:26
  • @AnthonyWebber Sure. I added a section to the answer to show how to force a renumbering of the indices. – John1024 Sep 19 '17 at 1:17
  • 2
    Just to mention an alternative approach (which might be a better fit for some code): instead of unset numbers[4], assign the whole array using slicing, i.e. numbers=("${numbers[@]:0:4}" "${numbers[@]:5}") (I would post as an answer, but don't have time to properly explain). – Toby Speight Sep 19 '17 at 8:03
  • @John1024: Appreciate you doing that. And thnx Toby :) – Anthony Webber Sep 19 '17 at 9:37
5

bash arrays like in ksh, are not really arrays, they're more like associative arrays with keys limited to positive integers (or so called sparse arrays). For a shell with real arrays, you can have a look at shells like rc, es, fish, yash, zsh (or even csh/tcsh though those shells have so many issues they're better avoided).

In zsh:

a=(1 2 3 4 5)
a[3]=() # remove the 3rd element
a[1,3]=() # remove the first 3 elements
a[-1]=() # remove the last element

(Note that in zsh, unset 'a[3]' actually sets it to the empty string for improved compatibility with ksh)

in yash:

a=(1 2 3 4 5)
array -d a 3 # remove the 3rd element
array -d a 1 2 3 # remove the first 3 elements
array -d a -1 # remove the last element

in fish (not a Bourne-like shell contrary to bash/zsh):

set a 1 2 3 4 5
set -e a[3] # remove the 3rd element
set -e a[1..3] # remove the first 3 elements
set -e a[-1] # remove the last element

in es (based on rc, not Bourne-like)

a = 1 2 3 4 5
a = $a(... 2 4 ...) # remove the 3rd element
a = $a(4 ...) # remove the first 3 elements
a = $a(... `{expr $#a - 1}) # remove the last element
# or a convoluted way that avoids forking expr:
a = $a(... <={@{*=$*(2 ...); return $#*} $a})

in ksh and bash

You can use the arrays as normal arrays if you do:

a=("${a[@]}")

after each delete or insert operations that may have made the list of indexes not contiguous or not start at 0. Also note that ksh/bash arrays start at 0, not 1 (except for $@ (in some ways)).

That will in effect tidy the elements and move them to index 0, 1, 2... in sequence.

Also note that you need to quote the number[i] in:

unset 'number[i]'

Otherwise, that would be treated as unset numberi is there was a file called numberi in the current directory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.