Como faço para mudar um array bash em algum índice no meio?

10
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.

Por que, na linha 13, o stdout está em branco, considerando que o array parece ter sido atualizado julgando pelo stdout da linha 12?

E, portanto, o que devo fazer para obter a resposta pretendida, "69"?

    
por Anthony Webber 19.09.2017 / 00:48

2 respostas

20

unset remove um elemento. Não renumerar os elementos restantes.

Podemos usar declare -p para ver exatamente o que acontece com 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 que numbers não possui mais um elemento 4 .

Outro exemplo

Observe:

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

A matriz a não possui elementos de 2 a 21. O Bash não exige que os índices da matriz sejam consecutivos.

Método sugerido para forçar uma renumeração dos índices

Vamos começar com a matriz numbers com o elemento em falta 4 :

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

Se quisermos que os índices mudem, então:

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

Existe agora um número de elemento 4 e o valor 69 .

Método alternativo para remover um elemento & renumerar array em uma etapa

Mais uma vez, vamos definir numbers :

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

Como sugerido por Toby Speight nos comentários, um método para remover o quarto elemento e renumerar o elementos restantes em um único passo:

$ 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")

Como você pode ver, o quarto elemento foi removido e todos os demais elementos foram renumerados.

${numbers[@]:0:4} array de fatias numbers : leva os primeiros quatro elementos começando com o elemento 0.

Da mesma forma, ${numbers[@]:5} matriz de partições numbers : leva todos os elementos, começando pelo elemento 5 e continuando até o final da matriz.

Obtendo os índices de uma matriz

Os valores de uma matriz podem ser obtidos com ${a[@]} . Para encontrar os índices (ou chaves ) que correspondem a esses valores, use ${!a[@]} .

Por exemplo, considere novamente nossa matriz numbers com o elemento ausente 4 :

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

Para ver quais índices são atribuídos:

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

Novamente, 4 está faltando na lista de índices.

Documentação

De 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.

    
por 19.09.2017 / 01:08
5

bash matrizes como em ksh , não são realmente matrizes, elas são mais como matrizes associativas com chaves limitadas a inteiros positivos (ou assim chamados matrizes esparsas ). Para um shell com arrays reais, você pode dar uma olhada nos shells como rc , es , fish , yash , zsh (ou mesmo csh / tcsh , embora essas conchas tenham tantos questões são melhor evitadas).

Em 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

(Observe que, em zsh, unset 'a[3]' , na verdade, define a string vazia para compatibilidade aprimorada com ksh )

em 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

em fish (não é uma shell parecida com Bourne ao contrário de 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

em es (com base em rc , não em 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})

em ksh e bash

Você pode usar as matrizes como matrizes normais se fizer isso:

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

após cada exclusão ou inserção de operações que podem ter tornado a lista de índices não contíguos ou não iniciados em 0. Observe também que as matrizes ksh / bash começam em 0, não em 1 (exceto $@ (em algumas maneiras)).

Isso vai arrumar os elementos e movê-los para o índice 0, 1, 2 ... em seqüência.

Observe também que você precisa citar o number[i] em:

unset 'number[i]'

Caso contrário, isso seria tratado como unset numberi se houvesse um arquivo chamado numberi no diretório atual.

    
por 19.09.2017 / 14:06