Cómo declarar una matriz 2D en bash

Me pregunto cómo declarar una matriz 2D en bash y luego inicializar a 0.

En C se ve así:

int a[4][5] = {0}; 

¿Y cómo asigno un valor a un elemento? Como en C:

 a[2][3] = 3; 

Puede simularlos, por ejemplo, con hash, pero necesita tener cuidado con los ceros iniciales y muchas otras cosas. La próxima demostración funciona, pero está lejos de ser la solución óptima.

 #!/bin/bash declare -A matrix num_rows=4 num_columns=5 for ((i=1;i<=num_rows;i++)) do for ((j=1;j<=num_columns;j++)) do matrix[$i,$j]=$RANDOM done done f1="%$((${#num_rows}+1))s" f2=" %9s" printf "$f1" '' for ((i=1;i<=num_rows;i++)) do printf "$f2" $i done echo for ((j=1;j<=num_columns;j++)) do printf "$f1" $j for ((i=1;i<=num_rows;i++)) do printf "$f2" ${matrix[$i,$j]} done echo done 

el ejemplo anterior crea una matriz de 4x5 con números aleatorios e imprime la transposición, con el resultado del ejemplo

  1 2 3 4 1 18006 31193 16110 23297 2 26229 19869 1140 19837 3 8192 2181 25512 2318 4 3269 25516 18701 7977 5 31775 17358 4468 30345 

El principio es: Crear una matriz asociativa donde el índice es una cadena como 3,4 . Los beneficios:

  • es posible usar para matrices de cualquier dimensión;) como: 30,40,2 para 3 dimensiones.
  • la syntax está cerca de "C" como matrices ${matrix[2,3]}

Bash no admite matrices multidimensionales.

Sin embargo, puedes simularlo mediante el uso de expansión indirecta:

 #!/bin/bash declare -a a0=(1 2 3 4) declare -a a1=(5 6 7 8) var="a1[1]" echo ${!var} # outputs 6 

Las asignaciones también son posibles con este método:

 let $var=55 echo ${a1[1]} # outputs 55 

Edición 1 : para leer dicha matriz desde un archivo, con cada fila en una línea, y los valores delimitados por el espacio, use esto:

 idx=0 while read -aa$idx; do let idx++; done  

Editar 2 : para declarar e inicializar a0..a3[0..4] a 0 , podría ejecutar:

 for i in {0..3}; do eval "declare -aa$i=( $(for j in {0..4}; do echo 0; done) )" done 

Bash no tiene matriz multidimensional. Pero puede simular un efecto algo similar con matrices asociativas. El siguiente es un ejemplo de matriz asociativa que pretende ser utilizada como matriz multidimensional:

 declare -A arr arr[0,0]=0 arr[0,1]=1 arr[1,0]=2 arr[1,1]=3 echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1 

Si no declaras la matriz como asociativa (con -A ), lo anterior no funcionará. Por ejemplo, si omite la línea declare -A arr , el echo imprimirá 2 3 lugar de 0 1 , porque 0,0 , 1,0 y tal se tomará como expresión aritmética y se evaluará a 0 (el valor a la derecha) del operador de coma).

También puedes acercarte a esto de una forma mucho menos inteligente

 q=() q+=( 1-2 ) q+=( ab ) for set in ${q[@]}; do echo ${set%%-*} echo ${set##*-} done 

por supuesto, una solución de 22 líneas o una indirección es probablemente la mejor manera de hacerlo y por qué no esparcir eval en todas partes.

Una forma de simular matrices en bash (se puede adaptar para cualquier cantidad de dimensiones de una matriz):

 #!/bin/bash ## The following functions implement vectors (arrays) operations in bash: ## Definition of a vector : ## v_0 - variable that stores the number of elements of the vector ## v_1..v_n, where n=v_0 - variables that store the values of the vector elements VectorAddElementNext () { # Vector Add Element Next # Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1 local elem_value local vector_length local elem_name eval elem_value=\"\$$2\" eval vector_length=\$$1\_0 if [ -z "$vector_length" ]; then vector_length=$((0)) fi vector_length=$(( vector_length + 1 )) elem_name=$1_$vector_length eval $elem_name=\"\$elem_value\" eval $1_0=$vector_length } VectorAddElementDVNext () { # Vector Add Element Direct Value Next # Adds the string $2 in the next element position (vector length + 1) in vector $1 local elem_value local vector_length local elem_name eval elem_value="$2" eval vector_length=\$$1\_0 if [ -z "$vector_length" ]; then vector_length=$((0)) fi vector_length=$(( vector_length + 1 )) elem_name=$1_$vector_length eval $elem_name=\"\$elem_value\" eval $1_0=$vector_length } VectorAddElement () { # Vector Add Element # Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1 local elem_value local elem_position local vector_length local elem_name eval elem_value=\"\$$3\" elem_position=$(($2)) eval vector_length=\$$1\_0 if [ -z "$vector_length" ]; then vector_length=$((0)) fi if [ $elem_position -ge $vector_length ]; then vector_length=$elem_position fi elem_name=$1_$elem_position eval $elem_name=\"\$elem_value\" if [ ! $elem_position -eq 0 ]; then eval $1_0=$vector_length fi } VectorAddElementDV () { # Vector Add Element # Adds the string $3 in the position $2 (variable or direct value) in the vector $1 local elem_value local elem_position local vector_length local elem_name eval elem_value="$3" elem_position=$(($2)) eval vector_length=\$$1\_0 if [ -z "$vector_length" ]; then vector_length=$((0)) fi if [ $elem_position -ge $vector_length ]; then vector_length=$elem_position fi elem_name=$1_$elem_position eval $elem_name=\"\$elem_value\" if [ ! $elem_position -eq 0 ]; then eval $1_0=$vector_length fi } VectorPrint () { # Vector Print # Prints all the elements names and values of the vector $1 on sepparate lines local vector_length vector_length=$(($1_0)) if [ "$vector_length" = "0" ]; then echo "Vector \"$1\" is empty!" else echo "Vector \"$1\":" for ((i=1; i<=$vector_length; i++)); do eval echo \"[$i]: \\\"\$$1\_$i\\\"\" ###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\" done fi } VectorDestroy () { # Vector Destroy # Empties all the elements values of the vector $1 local vector_length vector_length=$(($1_0)) if [ ! "$vector_length" = "0" ]; then for ((i=1; i<=$vector_length; i++)); do unset $1_$i done unset $1_0 fi } ################## ### MAIN START ### ################## ## Setting vector 'params' with all the parameters received by the script: for ((i=1; i<=$#; i++)); do eval param="\${$i}" VectorAddElementNext params param done # Printing the vector 'params': VectorPrint params read temp ## Setting vector 'params2' with the elements of the vector 'params' in reversed order: if [ -n "$params_0" ]; then for ((i=1; i<=$params_0; i++)); do count=$((params_0-i+1)) VectorAddElement params2 count params_$i done fi # Printing the vector 'params2': VectorPrint params2 read temp ## Getting the values of 'params2'`s elements and printing them: if [ -n "$params2_0" ]; then echo "Printing the elements of the vector 'params2':" for ((i=1; i<=$params2_0; i++)); do eval current_elem_value=\"\$params2\_$i\" echo "params2_$i=\"$current_elem_value\"" done else echo "Vector 'params2' is empty!" fi read temp ## Creating a two dimensional array ('a'): for ((i=1; i<=10; i++)); do VectorAddElement a 0 i for ((j=1; j<=8; j++)); do value=$(( 8 * ( i - 1 ) + j )) VectorAddElementDV a_$i $j $value done done ## Manually printing the two dimensional array ('a'): echo "Printing the two-dimensional array 'a':" if [ -n "$a_0" ]; then for ((i=1; i<=$a_0; i++)); do eval current_vector_lenght=\$a\_$i\_0 if [ -n "$current_vector_lenght" ]; then for ((j=1; j<=$current_vector_lenght; j++)); do eval value=\"\$a\_$i\_$j\" printf "$value " done fi printf "\n" done fi ################ ### MAIN END ### ################ 

Otro enfoque es que puede representar cada fila como una cadena, es decir, mapear la matriz 2D en una matriz 1D. Luego, todo lo que tiene que hacer es desempaquetar y volver a empaquetar la cadena de la fila cada vez que realice una edición:

 # Init a 4x5 matrix a=("0 0 0 0 0" "0 0 0 0 0" "0 0 0 0 0" "0 0 0 0 0") function aset { IFS=' ' read -r -a tmp <<< "${a[$1]}" tmp[$2]=$3 a[$1]="${tmp[@]}" } # Set a[2][3] = 3 aset 2 3 3 # Show result for r in "${a[@]}"; do echo $r done 

Productos:

 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 

Uno puede simplemente definir dos funciones para escribir ($ 4 es el valor asignado) y leer una matriz con nombre arbitrario ($ 1) e índices ($ 2 y $ 3) que explotan las referencias de evaluación e indirecta.

 #!/bin/bash matrix_write () { eval $1"_"$2"_"$3=$4 # aux=$1"_"$2"_"$3 # Alternative way # let $aux=$4 # --- } matrix_read () { aux=$1"_"$2"_"$3 echo ${!aux} } for ((i=1;i<10;i=i+1)); do for ((j=1;j<10;j=j+1)); do matrix_write a $i $j $[$i*10+$j] done done for ((i=1;i<10;i=i+1)); do for ((j=1;j<10;j=j+1)); do echo "a_"$i"_"$j"="$(matrix_read a $i $j) done done 

Si cada fila de la matriz tiene el mismo tamaño, entonces simplemente puede usar una matriz lineal y una multiplicación.

Es decir,

 a=() for (( i=0; i<4; ++i )); do for (( j=0; j<5; ++j )); do a[i*5+j]=0 done done 

Entonces tu a[2][3] = 3 convierte

 a[2*5+3] = 3 

Puede valer la pena convertir este enfoque en un conjunto de funciones, pero como no puede pasar matrices o devolver matrices desde funciones, debería usar pass-by-name y, a veces, eval . Así que tiendo a archivar arreglos multidimensionales bajo "cosas bash es simplemente no significa hacer".

Para simular una matriz bidimensional, primero cargo los primeros n elementos (los elementos de la primera columna)

 local pano_array=() i=0 for line in $(grep "filename" "$file") do url=$(extract_url_from_xml $line) pano_array[i]="$url" i=$((i+1)) done 

Para agregar la segunda columna, defino el tamaño de la primera columna y calculo los valores en una variable de compensación

 array_len="${#pano_array[@]}" i=0 while [[ $i -lt $array_len ]] do url="${pano_array[$i]}" offset=$(($array_len+i)) found_file=$(get_file $url) pano_array[$offset]=$found_file i=$((i+1)) done