How do I shift a bash array at some index in the middle?

Ant*_*ber 15 bash array variable

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.
Run Code Online (Sandbox Code Playgroud)

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"?

Joh*_*024 22

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")
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

To see which indices are assigned:

$ echo "${!numbers[@]}"
0 1 2 3 5 6 7 8
Run Code Online (Sandbox Code Playgroud)

Again, 4 is missing from the list of indices.

Documentation

From man bash:

unset内建是用来摧毁阵列。 unset name[subscript]销毁 index 处的数组元素subscript。索引数组的负下标按上述解释。必须小心避免由路径名扩展引起的不必要的副作用。 unset name, where nameis an array, or unset name[subscript], where subscriptis * or @, 删除整个数组。

  • 只是提到一种替代方法(这可能更适合某些代码):而不是`unset numbers[4]`,使用切片分配整个数组,即`numbers=("${numbers[@]:0: 4}" "${numbers[@]:5}")`(我会作为答案发布,但没有时间正确解释)。 (3认同)

Sté*_*las 6

bash像 inksh这样的数组并不是真正的数组,它们更像是键限制为正整数的关联数组(或所谓的稀疏数组)。对于具有真实数组的外壳,您可以查看诸如rc, es, fish, yash, 之类的外壳zsh(甚至csh/tcsh尽管这些外壳有很多问题,但最好避免)。

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
Run Code Online (Sandbox Code Playgroud)

(注意,在 zsh 中,unset 'a[3]'实际上将其设置为空字符串以提高与 的兼容性ksh

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
Run Code Online (Sandbox Code Playgroud)

in fish(不是与bash/相反的类似 Bourne 的外壳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
Run Code Online (Sandbox Code Playgroud)

in es(基于rc,不是 Bourne 式的)

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})
Run Code Online (Sandbox Code Playgroud)

kshbash

如果您这样做,您可以将数组用作普通数组:

a=("${a[@]}")
Run Code Online (Sandbox Code Playgroud)

在每次删除或插入操作之后,可能使索引列表不连续或不从 0 开始。还要注意ksh/bash数组从 0 开始,而不是 1(除了$@(在某些方面))。

这实际上会整理元素并将它们按顺序移动到索引 0、1、2...。

另请注意,您需要引用number[i]in:

unset 'number[i]'
Run Code Online (Sandbox Code Playgroud)

否则,unset numberi如果numberi在当前目录中调用了一个文件,它将有效地运行。