How do I shift a bash array at some index in the middle?
Solution 1
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.
Solution 2
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 effectively run unset numberi
if there was a file called numberi
in the current directory.
Related videos on Youtube
Anthony Webber
Updated on September 18, 2022Comments
-
Anthony Webber over 1 year
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"?
-
Wildcard over 6 yearsConsidering the type of coding work this question implies, you should take warning: see Is there something wrong with my script or is Bash much slower than Python?
-
-
chepner over 6 yearsNote 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 variablea
is still undefined until you actually assign to one of its indices. -
Anthony Webber over 6 years@John1024: Thank you for this answer. Could you possibly expand it to include a suggested answer for achieving the intended result?
-
Toby Speight over 6 yearsJust 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). -
Anthony Webber over 6 years@John1024: Appreciate you doing that. And thnx Toby :)
-
U. Windl about 3 yearsI think the manual page for bash should be changed regarding "unset name[subscript] destroys the array element at index subscript.". Maybe "unset name[subscript] destroys the array element with index subscript." would be enough, or emphasize that bash has arrays allowing for non-consecutive indices (just like a hash table where the keys are natural numbers including zero).