¿Cómo cambio una matriz bash en algún índice en el medio?

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 qué, en la línea 13, el stdout está en blanco, considerando que la matriz parece haberse actualizado a juzgar por el stdout de la línea 12?

Y por lo tanto, ¿qué debo hacer para obtener la respuesta deseada, "69"?

Anthony Webber
fuente
1
Teniendo en cuenta el tipo de trabajo de codificación que implica esta pregunta, debe tener cuidado: consulte ¿Hay algún problema con mi script o Bash es mucho más lento que Python?
Comodín el

Respuestas:

21

unsetelimina un elemento No renumera los elementos restantes.

Podemos usar declare -ppara ver exactamente qué sucede con 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 numbersya no tiene un elemento 4.

Otro ejemplo

Observar:

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

La matriz ano tiene elementos del 2 al 21. Bash no requiere que los índices de la matriz sean consecutivos.

Método sugerido para forzar una nueva numeración de los índices.

Comencemos con la numbersmatriz con el elemento que falta 4:

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

Si queremos que cambien los índices, entonces:

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

Ahora hay un número de elemento 4y tiene valor 69.

Método alternativo para eliminar un elemento y renumerar la matriz en un solo paso

Nuevamente, definamos numbers:

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

Como lo sugiere Toby Speight en los comentarios, un método para eliminar el cuarto elemento y volver a numerar los elementos restantes en un solo paso:

$ 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 puede ver, el cuarto elemento se eliminó y todos los elementos restantes se volvieron a numerar.

${numbers[@]:0:4}matriz de rebanadas numbers: toma los primeros cuatro elementos comenzando con el elemento 0.

Del mismo modo, ${numbers[@]:5}matriz de corte numbers: toma todos los elementos que comienzan con el elemento 5 y continúan hasta el final de la matriz.

Obteniendo los índices de una matriz

Los valores de una matriz se pueden obtener con ${a[@]}. Para encontrar los índices (o claves ) que corresponden a esos valores, use ${!a[@]}.

Por ejemplo, considere nuevamente nuestra matriz numberscon el elemento que falta 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 qué índices se asignan:

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

Nuevamente, 4falta en la lista de índices.

Documentación

De man bash:

El unsetbuiltin se usa para destruir matrices. unset name[subscript]destruye el elemento de matriz en el índice subscript. Los subíndices negativos a las matrices indexadas se interpretan como se describe anteriormente. Se debe tener cuidado para evitar los efectos secundarios no deseados causados ​​por la expansión del nombre de ruta. unset name, where namees una matriz, o unset name[subscript], where subscriptis * o @, elimina la matriz completa.

John1024
fuente
1
Tenga en cuenta que la sintaxis de matriz de shell es realmente solo una forma de facilitar el manejo de variables con nombres similares. No hay una matriz en sí misma; de hecho, después de escribir a=(), la variable aaún no está definida hasta que realmente asigne uno de sus índices.
chepner
@ John1024: Gracias por esta respuesta. ¿Podría expandirlo para incluir una respuesta sugerida para lograr el resultado deseado?
Anthony Webber
@AnthonyWebber Claro. Agregué una sección a la respuesta para mostrar cómo forzar una nueva numeración de los índices.
John1024
2
Solo para mencionar un enfoque alternativo (que podría ser mejor para algún código): en lugar de unset numbers[4]asignar toda la matriz usando la división, es decir numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(publicaría como respuesta, pero no tengo tiempo para explicarlo adecuadamente).
Toby Speight
@ John1024: Te agradezco que hagas eso. Y gracias Toby :)
Anthony Webber
5

bashLas matrices como en ksh, no son realmente matrices, son más bien matrices asociativas con claves limitadas a enteros positivos (o llamadas matrices dispersas ). Para un depósito con matrices reales, se puede echar un vistazo a como conchas rc, es, fish, yash, zsh(o incluso csh/ tcshaunque esos proyectiles tienen tantos problemas que están mejor evitado).

En 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

(Tenga en cuenta que en zsh, en unset 'a[3]'realidad lo establece en la cadena vacía para mejorar la compatibilidad con ksh)

en 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

en fish(no un shell Bourne contrario 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

en es(basado en rc, no como 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})

en kshybash

Puede usar las matrices como matrices normales si hace lo siguiente:

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

después de cada operación de eliminación o inserción que puede haber hecho que la lista de índices no sea contigua o no comience en 0. También tenga en cuenta que ksh/ basharrays comienzan en 0, no en 1 (excepto $@(en algunos aspectos)).

De hecho, eso ordenará los elementos y los moverá al índice 0, 1, 2 ... en secuencia.

También tenga en cuenta que necesita citar el number[i]en:

unset 'number[i]'

De lo contrario, eso se trataría como si unset numberihubiera un archivo llamado numberien el directorio actual.

Stéphane Chazelas
fuente