Como altero uma matriz bash em algum índice no meio?

12
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 a julgar pelo stdout da linha 12?

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

Anthony Webber
fonte
1
Considerando o tipo de trabalho de codificação que esta pergunta implica, você deve receber um aviso: consulte Há algo de errado com meu script ou o Bash é muito mais lento que o Python?
Curinga

Respostas:

21

unsetremove um elemento Não renumera os elementos restantes.

Podemos usar declare -ppara 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 numbersnão há mais um elemento 4.

Outro exemplo

Observar:

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

A matriz anão possui elementos 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 numbersmatriz 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")

Se gostaríamos que os índices mudassem, 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")

Agora existe um número de elemento 4e ele tem valor 69.

Método alternativo para remover um elemento e renumerar uma matriz em uma etapa

Novamente, vamos definir numbers:

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

Conforme sugerido por Toby Speight nos comentários, um método para remover o quarto elemento e renumerar os elementos restantes em uma única etapa:

$ 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 elementos restantes foram renumerados.

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

Da mesma forma, ${numbers[@]:5}array de fatias numbers: leva todos os elementos começando com o elemento 5 e continuando até o final do array.

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 numberscom 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 estão atribuídos:

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

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

Documentação

De man bash:

O unsetbuiltin é usado para destruir matrizes. unset name[subscript]destrói o elemento da matriz no índice subscript. Os subscritos negativos para matrizes indexadas são interpretados como descrito acima. Deve-se tomar cuidado para evitar efeitos colaterais indesejados causados ​​pela expansão do nome do caminho. unset name, onde nameé uma matriz, ou unset name[subscript], onde subscripté * ou @, remove a matriz inteira.

John1024
fonte
1
Observe que a sintaxe do array de shell é realmente apenas uma maneira de facilitar o tratamento de variáveis ​​com nomes semelhantes. Não existe uma matriz em si; de fato, depois que você escreve a=(), a variável aainda é indefinida até você realmente atribuir a um de seus índices.
Chepner
@ John1024: Obrigado por esta resposta. Você poderia expandi-lo para incluir uma resposta sugerida para alcançar o resultado pretendido?
Anthony Webber
@AnthonyWebber Sure. Adicionei uma seção à resposta para mostrar como forçar uma renumeração dos índices.
precisa saber é o seguinte
2
Apenas para mencionar uma abordagem alternativa (que pode ser mais adequada para algum código): em vez de unset numbers[4]atribuir toda a matriz usando o fatiamento, ou seja numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(eu postaria como resposta, mas não tenho tempo para explicar adequadamente).
precisa saber é o seguinte
@ John1024: Agradeço por fazer isso. E thnx Toby :)
Anthony Webber
5

bashmatrizes como in ksh, não são realmente matrizes, são mais como matrizes associativas com chaves limitadas a números inteiros positivos (ou as chamadas matrizes esparsas ). Para um reservatório com matrizes reais, você pode dar uma olhada em escudos como rc, es, fish, yash, zsh(ou mesmo csh/ tcshque essas conchas têm tantos problemas que eles estão melhor evitar).

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, no zsh, unset 'a[3]'na verdade , o define como a sequência vazia para melhor compatibilidade 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

in fish(não é um shell tipo Bourne contrário a 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(baseado em rc, não semelhante a 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})

dentro kshebash

Você pode usar as matrizes como matrizes normais se:

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

depois de cada operação de exclusão ou inserção que possa ter tornado a lista de índices não contígua ou não iniciada em 0. Observe também que ksh/ bashmatrizes iniciam em 0, não 1 (exceto $@(de certa forma)).

Com efeito, isso arrumará os elementos e os moverá para o índice 0, 1, 2 ... em sequência.

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

unset 'number[i]'

Caso contrário, isso seria tratado como unset numberise houvesse um arquivo chamado numberino diretório atual.

Stéphane Chazelas
fonte