2012-06-27 92 views
32

Estoy planeando un script para administrar algunas piezas de mis sistemas Linux y estoy a punto de decidir si quiero usar o .Arrays multidimensionales en Bash

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. El almacenamiento simple tecla = pares de valores en los archivos de configuración es bastante fácil con bash, pero la única manera 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 Marshall/El código de unmarshall podría llegar a ser un oso y está lejos de ser fácil de usar para la próxima savia pobre 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 una manera fácil de hacer esto en bash?

gracias a todos.

+0

Relacionado: [Cómo declarar una matriz 2D en bash] (http://stackoverflow.com/q/16487258/55075) – kenorb

Respuesta

29

Bash no admite matrices multidimensionales, ni hash, y parece que quiere un hash que los valores son matrices. Esta solución no es muy bonito, una solución con un archivo XML debe 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[@]}" 
+0

Esa es la respuesta que esperaba, pero nunca corazones para preguntar. Gracias – scphantm

+16

Solo una nota. bash does hashes de soporte (matrices asociativas) a partir de la versión 4. más información: http://mywiki.wooledge.org/BashFAQ/006 – poncha

+0

FYI matrices asociativas pueden tener algunos de los atributos 'declarar' establecidos en ellos, como 'establecer a mayúsculas en la asignación 'sin embargo, no pueden tener la matriz -A [asociativa] o -a [numérica] establecida para ellos, ni la -n conjunto de referencia, pero PUEDE etiquetar su nombre de variable con un número, y usar una variable en lugar: MYARR_ $ i _ [$ j] sería lo más parecido a esto, sin embargo, no es una verdadera matriz de md, pero esto es lo mejor que obtendrás. También podría usar funciones como un sistema de pseudo-matriz si estuviera lo suficientemente desesperado :) – osirisgothra

8

independiente de la cáscara se utiliza (sh, ksh, golpe, ...) el siguiente método funciona bastante bien para n- matrices 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 (2ª dimensión) se utiliza la herramienta estándar Unix tr. Se pueden usar separadores adicionales para dimensiones adicionales de la misma manera.

Por supuesto, el cumplimiento de este enfoque no es muy bueno, pero si el rendimiento no es un criterio de 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 

La salida de esa muestra es el siguiente:

1.1  1.2  1.3  
2.1  2.2  
3.1  3.2  3.3  3.4 
+0

No funciona muy bien si los valores contienen espacios o dos puntos, sin embargo. :) – dannysauer

+1

@dannysauer - debería estar bien si codifica espacios y dos puntos como una entidad o código hexadecimal. % 20 y% 3a por ejemplo. Debería ser bastante fácil procesar la entrada y la salida de la matriz a través de tales funciones si eso le preocupa. – ghoti

+0

Entonces, los valores no contienen espacios o dos puntos. : D – dannysauer

-2
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 
+1

¿Le gustaría agregar algunas palabras explicativas sobre lo que se supone que debe hacer el código? Si bien algunos formatos también son útiles, algunos comentarios mejorarían aún más la respuesta. Aunque puedo ver matrices múltiples, no veo una matriz multidimensional aquí. – Izzy

7

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 

Se basa fuera de this answer here

+0

debe ser 'NAME = $ {! MAIN_ARRAY [i]: 0: 1}' y 'VALOR = $ {! MAIN_ARRAY [i]: 1: 1}' si no me equivoco para que esto funcione (es decir. intercambiar 'URLS' por' MAIN'). – Christian

2

Ampliando la respuesta de Pablo - aquí está mi versión de trabajar con asociativos submatrices 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 una mezcla de valores de la matriz principal - cadenas/arrays/assoc.matrices

La clave aquí es para envolver los subconjuntos entre comillas simples y utilizar * en lugar de @ al almacenar un subconjunto dentro de la matriz principal, así que conseguiría almacenada como una sola, espacio de cadena separados: "${SUB_1[*]}"

Entonces hace que sea fácil de analizar una matriz de que cuando bucle a través de los valores con IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}

El código anterior salidas:

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

De modo que no puede almacenar cadenas que contienen espacios (sin mencionar nuevas líneas).Además, su falta de citas hace que sea imposible tener personajes glob de forma segura. –

+0

Sí, me acabo de dar cuenta de que romperá cadenas con espacios en subvalores separados, pero yo, personalmente, puedo vivir con eso si solo se trata de valores de configuración ... Pero creo que eso es lo mejor que podemos hacer en bash . Aunque estoy abierto a sugerencias/mejoras. Se agregaron las comillas, gracias por señalar :) –

+0

Hola, ¿puedes explicar cómo funciona 'IFS = '' leer -a val <<< $ {MAIN_ARRAY [$ key]}' trabajo. Que es IFS? ¿Es una variable que se define allí? ¿Por qué leer se agrega a la definición de variable? – Boyang

5

Bash d 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 utilizado 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 se declara la matriz como asociativa (con -A), lo anterior no funcionará. Por ejemplo, si se omite la línea declare -A arr, la echo imprimirá 2 3 en lugar de 0 1, porque 0,0, 1,0 y como será tomado como expresión aritmética y evaluados para 0 (el valor a la derecha del operador coma).

+1

declare -A [nombre de array] provoca una "opción inválida", al menos en OS X. – ktappe

+5

EDITAR: Parece que OS X viene con bash 3 y esta opción requiere bash 4. Gracias, Apple. – ktappe

2

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 usted quiere encontrar todos en Perú

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

Sólo grep (SED) en el tercer campo

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

Si sólo desea campo x

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

Todos en Perú que se llama Thomas y simplemente volver a su apellido

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

Cualquier consulta que se pueda imaginar ... SuperEasy.

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 base a 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 desea hacer eso

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

El Gotcha única Iv'e encontrado con esto es que necesidad hay que proporcionar los var (en el ejemplo; y ambas var i) o las cosas van a tener este aspecto

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

y alguien, sin duda, van a decir que no funcionará si tiene espacios en su entrada, sin embargo, que se puede fijar utilizando otro delimitador en su entrada, por ejemplo, (utilizando un carbón utf8 ahora hacer hincapié en que se puede elegir algo que su entrada no contendrá, pero se puede elegir cualquier OFC):

$ 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, se podría convertir el salto de línea a algo más antes de la entrada y convertirlo nuevamente en la salida (o no usar bash ...). ¡Disfrutar!

+0

Esto puede funcionar para dos dimensiones, pero para una caja genérica con n dimensiones esto se está poniendo difícil. –

+0

Eso es verdad. Cuando escribí esto, no me di cuenta de la diferencia entre 2d, 3d, 4d y todo lo que necesitaba era un buen soporte tabular/db. Ahora, todavía no he necesitado nada más que 2d, pero si uno necesita más, entonces supongo que esto no funcionará. – n00p

0

que hacerlo utilizando matrices asociativas desde fiesta 4 y el establecimiento de IFS a un valor que se puede definir de forma manual.

El propósito de este enfoque es tener matrices como valores de claves de matriz asociativas.

Para volver a configurar IFS en su valor predeterminado, simplemente desconéctelo.

  • unset IFS

Este 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 

La salida 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 
+0

No veo ninguna razón para usar 'test = (" x3: x4: x5 ")' cuando lo referencia como '$ test': Simplemente podría hacer' test = "x3: x4: x5" '. –

+0

Sí, tienes toda la razón. Gracias por la pista. – rocksteady

1

estoy publicando el siguiente porque yo Es una forma muy simple y clara de imitar (al menos hasta cierto punto) el comportamiento de una matriz bidimensional en Bash.Se utiliza un aquí-archivo (consulte el manual de Bash) y read (un comando Bash orden interna):

## 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 people  # Create an indexed array (necessary for the read command).                     
while read -ra people; do 
    firstName=${people[0]} 
    familyName=${people[1]} 
    birthYear=${people[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:

  • El 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 read en el manual de Bash) sepa califica los elementos de estos vectores.
  • Luego, utilizando el comando 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 read impide que las barras invertidas actúen como caracteres de escape, en caso de que hayamos escrito barras invertidas en el documento aquí physicists.$$.

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

+0

Es muy parecido a la solución de user7909577. Sin embargo, lo que haces es escribir una secuencia de líneas en un archivo de texto. Entonces, la dimensión 1 es el número de línea. La dimensión 2 es la palabra en una línea. Una solución verdaderamente genérica, sin embargo, permitiría poner cualquier matriz en cualquier matriz. Y eso no funcionará OK, podrías implementar las dimensiones al sangrar las líneas, pero eso aún no es muy elegante. –

+0

@U. Windi "pone cualquier matriz en cualquier arreglo. Y eso no funcionará": Por eso enfatizo en mi respuesta que es para simular 2D-ARRAYS. "Es muy similar a la solución de user7909577": no lo creo, analiza una cadena con sed/grep/awk en su respuesta, y lo hago "transformando" primero la cadena en una matriz, luego usando índices (es por eso que simula el comportamiento de una matriz). – Giuseppe

+0

@Guiseppe: Pero ambas soluciones usan un archivo de texto como representación para una matriz bidimensional con las dimensiones que describí. La pregunta específicamente era acerca de arreglos multidimensionales (hay una pregunta separada para arreglos bidimensionales). –

0

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 usted solicitó, pero a mí me funciona. (Lo mismo se puede lograr solo con variables sin el conjunto)

Cuestiones relacionadas