arrays bash shell multidimensional-array

arrays - Conjuntos multidimensionales en Bash



shell multidimensional-array (11)

Estoy planeando un script para administrar algunas piezas de mis sistemas Linux y estoy a punto de decidir si quiero usar bash o python .

Preferiría hacer esto como un script Bash simplemente porque los comandos son más fáciles, pero el factor decisivo es la configuración. Necesito poder almacenar una matriz multidimensional en el archivo de configuración para decirle al script qué hacer consigo mismo. Almacenar pares simples de clave = valor en archivos de configuración es bastante fácil con bash, pero la única forma en que se me ocurre hacer una matriz multidimensional es un motor de análisis de dos capas, algo así como

array=&d1|v1;v2;v3&d2|v1;v2;v3

pero el código marshall / unmarshall podría llegar a ser un oso y está lejos de ser fácil de usar para la próxima pobre savia que tiene que administrar esto. Si no puedo hacer esto fácilmente en bash, simplemente escribiré las configuraciones en un archivo xml y escribiré el script en python.

¿Hay alguna manera fácil de hacer esto en bash?

gracias a todos.


Ampliando la respuesta de Paul: esta es mi versión de trabajar con sub-arreglos asociativos en bash:

declare -A SUB_1=(["name1key"]="name1val" ["name2key"]="name2val") declare -A SUB_2=(["name3key"]="name3val" ["name4key"]="name4val") STRING_1="string1val" STRING_2="string2val" MAIN_ARRAY=( "${SUB_1[*]}" "${SUB_2[*]}" "${STRING_1}" "${STRING_2}" ) echo "COUNT: " ${#MAIN_ARRAY[@]} for key in ${!MAIN_ARRAY[@]}; do IFS='' '' read -a val <<< ${MAIN_ARRAY[$key]} echo "VALUE: " ${val[@]} if [[ ${#val[@]} -gt 1 ]]; then for subkey in ${!val[@]}; do subval=${val[$subkey]} echo "SUBVALUE: " ${subval} done fi done

Funciona con valores mixtos en la matriz principal - strings / arrays / assoc. matrices

La clave aquí es ajustar los subárboles en comillas simples y usar * lugar de @ cuando se almacena un subarreglo dentro de la matriz principal para que se almacene como una sola cadena separada de espacio: "${SUB_1[*]}"

Luego hace que sea fácil analizar un conjunto de eso al pasar por los valores con IFS='' '' read -a val <<< ${MAIN_ARRAY[$key]}

Los resultados del código anterior:

COUNT: 4 VALUE: name1val name2val SUBVALUE: name1val SUBVALUE: name2val VALUE: name4val name3val SUBVALUE: name4val SUBVALUE: name3val VALUE: string1val VALUE: string2val


Bash no admite matrices multidimensionales, ni hashes, y parece que quiere un hash que los valores sean matrices. Esta solución no es muy bonita, una solución con un archivo xml debería ser mejor:

array=(''d1=(v1 v2 v3)'' ''d2=(v1 v2 v3)'') for elt in "${array[@]}";do eval $elt;done echo "d1 ${#d1[@]} ${d1[@]}" echo "d2 ${#d2[@]} ${d2[@]}"


Bash no es compatible con una matriz multidimensional, pero podemos implementar el uso de una matriz asociada. Aquí los índices son la clave para recuperar el valor. Associate array está disponible en la versión bash 4.

#!/bin/bash declare -A arr2d rows=3 columns=2 for ((i=0;i<rows;i++)) do for ((j=0;j<columns;j++)) do arr2d[$i,$j]=$i done done for ((i=0;i<rows;i++)) do for ((j=0;j<columns;j++)) do echo ${arr2d[$i,$j]} 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).


Después de muchas pruebas y errores, en realidad encuentro la matriz multidimensional mejor, más clara y más fácil en bash es usar una var. Sí.

Ventajas: no tiene que recorrer una gran matriz, solo puede repetir "$ var" y usar grep / awk / sed. Es fácil y claro y puedes tener tantas columnas como quieras.

Ejemplo:

$ var=$(echo -e ''kris hansen oslo/nthomas jonson peru/nbibi abu johnsonville/njohnny lipp peru'') $ echo "$var" kris hansen oslo thomas johnson peru bibi abu johnsonville johnny lipp peru

Si quieres encontrar a todos en perú

$ echo "$var" | grep peru thomas johnson peru johnny lipp peru

Solo grep (sed) en el tercer campo

$ echo "$var" | sed -n -E ''/(.+) (.+) peru/p'' thomas johnson peru johnny lipp peru

Si solo quieres el campo x

$ echo "$var" | awk ''{print $2}'' hansen johnson abu johnny

Todos en Perú que se llama Thomas y solo devuelven su apellido

$ echo "$var" |grep peru|grep thomas|awk ''{print $2}'' johnson

Cualquier consulta que se te ocurra ... superosa.

Para cambiar un artículo:

$ var=$(echo "$var"|sed "s/thomas/pete/")

Para eliminar una fila que contiene "x"

$ var=$(echo "$var"|sed "/thomas/d")

Para cambiar otro campo en la misma fila en función de un valor de otro elemento

$ var=$(echo "$var"|sed -E "s/(thomas) (.+) (.+)//1 test /3/") $ echo "$var" kris hansen oslo thomas test peru bibi abu johnsonville johnny lipp peru

Por supuesto, el bucle también funciona si quieres hacer eso

$ for i in "$var"; do echo "$i"; done kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru

Lo único que encontré con esto es que siempre debes citar la var (en el ejemplo: var e i) o las cosas se verán así.

$ for i in "$var"; do echo $i; done kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru

y sin duda alguien dirá que no funcionará si tiene espacios en su entrada, sin embargo eso se puede arreglar usando otro delímetro en su entrada, por ejemplo, (usando un carácter utf8 ahora para enfatizar que puede elegir algo que su entrada no contener, pero puede elegir cualquier cosa c):

$ var=$(echo -e ''field one☥field two hello☥field three yes moin/nfield 1☥field 2☥field 3 dsdds aq'') $ for i in "$var"; do echo "$i"; done field one☥field two hello☥field three yes moin field 1☥field 2☥field 3 dsdds aq $ echo "$var" | awk -F ''☥'' ''{print $3}'' field three yes moin field 3 dsdds aq $ var=$(echo "$var"|sed -E "s/(field one)☥(.+)☥(.+)//1☥test☥/3/") $ echo "$var" field one☥test☥field three yes moin field 1☥field 2☥field 3 dsdds aq

Si desea almacenar nuevas líneas en su entrada, puede convertir la nueva línea en otra cosa antes de la entrada y convertirla nuevamente en la salida (o no use bash ...). ¡Disfrutar!


Esto es lo que funcionó para mí.

# Define each array and then add it to the main one SUB_0=("name0" "value0") SUB_1=("name1" "value1") MAIN_ARRAY=( SUB_0[@] SUB_1[@] ) # Loop and print it. Using offset and length to extract values COUNT=${#MAIN_ARRAY[@]} for ((i=0; i<$COUNT; i++)) do NAME=${!MAIN_ARRAY[i]:0:1} VALUE=${!MAIN_ARRAY[i]:1:1} echo "NAME ${NAME}" echo "VALUE ${VALUE}" done

Está basado en esta respuesta aquí


Estoy publicando lo siguiente porque es una forma muy simple y clara de imitar (al menos hasta cierto punto) el comportamiento de una matriz bidimensional en Bash. Utiliza un archivo aquí (ver el manual Bash) y read (un comando incorporado Bash):

## Store the "two-dimensional data" in a file ($$ is just the process ID of the shell, to make sure the filename is unique) cat > physicists.$$ <<EOF Wolfgang Pauli 1900 Werner Heisenberg 1901 Albert Einstein 1879 Niels Bohr 1885 EOF nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d '' '') # Number of lines of the here-file specifying the physicists. ## Extract the needed data declare -a person # Create an indexed array (necessary for the read command). while read -ra person; do firstName=${person[0]} familyName=${person[1]} birthYear=${person[2]} echo "Physicist ${firstName} ${familyName} was born in ${birthYear}" # Do whatever you need with data done < physicists.$$ ## Remove the temporary file rm physicists.$$

Salida: Physicist Wolfgang Pauli was born in 1900 Physicist Werner Heisenberg was born in 1901 Physicist Albert Einstein was born in 1879 Physicist Niels Bohr was born in 1885

La forma en que funciona:

  • Las líneas en el archivo temporal creado juegan el papel de vectores unidimensionales, donde los espacios en blanco (o cualquier carácter de separación que elija, vea la descripción del comando de read en el manual de Bash) separan los elementos de estos vectores.
  • Luego, utilizando el comando de read con su opción -a , recorremos cada línea del archivo (hasta que lleguemos al final del archivo). Para cada línea, podemos asignar los campos deseados (= palabras) a una matriz, que declaramos justo antes del bucle. La opción -r del comando de read evita que las barras invertidas actúen como caracteres de escape, en caso de que escribamos barras invertidas en los physicists.$$ documento aquí physicists.$$ .

En conclusión, un archivo se crea como una matriz 2D, y sus elementos se extraen utilizando un ciclo sobre cada línea, y utilizando la capacidad del comando de read para asignar palabras a los elementos de una matriz (indexada).

Mejoria leve:

En el código anterior, el archivo physicists.$$ se da como entrada al ciclo while, de modo que de hecho pasa al comando de read . Sin embargo, descubrí que esto causa problemas cuando tengo otro comando que me solicita entrada dentro del ciclo while. Por ejemplo, el comando de select espera la entrada estándar, y si se coloca dentro del ciclo while, tomará la entrada de los physicists.$$ , en lugar de solicitar en la línea de comandos para la entrada del usuario. Para corregir esto, uso la opción -u de read , que permite leer desde un descriptor de archivo. Solo tenemos que crear un descriptor de archivo (con el comando exec ) correspondiente a los physicists.$$ y dárselo a la opción -u de lectura, como en el siguiente código:

## Store the "two-dimensional data" in a file ($$ is just the process ID of the shell, to make sure the filename is unique) cat > physicists.$$ <<EOF Wolfgang Pauli 1900 Werner Heisenberg 1901 Albert Einstein 1879 Niels Bohr 1885 EOF nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d '' '') # Number of lines of the here-file specifying the physicists. exec {id_nuclei}<./physicists.$$ # Create a file descriptor stored in ''id_nuclei''. ## Extract the needed data declare -a person # Create an indexed array (necessary for the read command). while read -ra person -u "${id_nuclei}"; do firstName=${person[0]} familyName=${person[1]} birthYear=${person[2]} echo "Physicist ${firstName} ${familyName} was born in ${birthYear}" # Do whatever you need with data done ## Close the file descriptor exec {id_nuclei}<&- ## Remove the temporary file rm physicists.$$

Tenga en cuenta que el descriptor de archivo está cerrado al final.


Independientemente del shell que se use (sh, ksh, bash, ...) el siguiente enfoque funciona bastante bien para matrices n-dimensionales (la muestra cubre una matriz bidimensional).

En la muestra, el separador de línea (1ª dimensión) es el carácter de espacio. Para introducir un separador de campo (2da dimensión) se usa la herramienta tr unix estándar. Se pueden usar separadores adicionales para dimensiones adicionales de la misma manera.

Por supuesto, el rendimiento de este enfoque no es muy bueno, pero si el rendimiento no es un criterio, este enfoque es bastante genérico y puede resolver muchos problemas:

array2d="1.1:1.2:1.3 2.1:2.2 3.1:3.2:3.3:3.4" function process2ndDimension { for dimension2 in $* do echo -n $dimension2 " " done echo } function process1stDimension { for dimension1 in $array2d do process2ndDimension `echo $dimension1 | tr : " "` done } process1stDimension

El resultado de esa muestra se ve así:

1.1 1.2 1.3 2.1 2.2 3.1 3.2 3.3 3.4


Lo hago usando matrices asociativas desde bash 4 y estableciendo IFS en un valor que se puede definir manualmente.

El objetivo de este enfoque es tener matrices como valores de claves de matriz asociativas.

Para volver a establecer el IFS en modo predeterminado, simplemente desconéctelo.

  • unset IFS

Esto es un ejemplo:

#!/bin/bash set -euo pipefail # used as value in asscciative array test=( "x3:x4:x5" ) # associative array declare -A wow=( ["1"]=$test ["2"]=$test ) echo "default IFS" for w in ${wow[@]}; do echo " $w" done IFS=: echo "IFS=:" for w in ${wow[@]}; do for t in $w; do echo " $t" done done echo -e "/n or/n" for w in ${!wow[@]} do echo " $w" for t in ${wow[$w]} do echo " $t" done done unset IFS unset w unset t unset wow unset test

El resultado del script a continuación es:

default IFS x3:x4:x5 x3:x4:x5 IFS=: x3 x4 x5 x3 x4 x5 or 1 x3 x4 x5 2 x3 x4 x5


Tengo una solución bastante simple pero inteligente: simplemente defina la matriz con variables en su nombre. Por ejemplo:

for (( i=0 ; i<$(($maxvalue + 1)) ; i++ )) do for (( j=0 ; j<$(($maxargument + 1)) ; j++ )) do declare -a array$i[$j]=((Your rule)) done done

No sé si esto ayuda, ya que no es exactamente lo que pediste, pero funciona para mí. (Lo mismo se puede lograr solo con variables sin la matriz)


echo "Enter no of terms" read count for i in $(seq 1 $count) do t=` expr $i - 1 ` for j in $(seq $t -1 0) do echo -n " " done j=` expr $count + 1 ` x=` expr $j - $i ` for k in $(seq 1 $x) do echo -n "* " done echo "" done