How do I shift a bash array at some index in the middle?
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 indexsubscript
. 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
, wherename
is an array, orunset name[subscript]
, wheresubscript
is*
or@
, removes the entire array.
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.