2012-08-02 18 views
20

Después de buscar a fondo una forma de crear una matriz asociativa en bash, encontré que declare -A array hará el truco. Pero el problema es que solo es para la versión 4 de bash y la versión de bash que el servidor tiene en nuestro sistema es 3.2.16.Crear matriz asociativa en bash 3

¿Cómo puedo lograr algún tipo de hack asociativo tipo array en bash 3? Los valores serán pasados ​​a un script como

ARG=array[key]; 

./script.sh ${ARG} 

EDIT: Sé que puedo hacer esto en awk, u otras herramientas, pero estricta fiesta se necesita para el escenario que estoy tratando de resolver.

+0

Dado que bash 3 tiene matrices normales, solo tendrá que implementar matrices asociativas basadas en ellas. Pero en serio, ¿por qué sería eso necesario? –

+3

Consulte [BashFAQ/006] (http://mywiki.wooledge.org/BashFAQ/006). –

Respuesta

25

Bash 3 no tiene matrices asociativas, por lo que tendrá que usar algunas otras características de idioma para su propósito. Tenga en cuenta que incluso bajo bash 4, el código que escribió no hace lo que dice que hace: ./script.sh ${ARG} no pasa la matriz asociativa a la secuencia de comandos secundaria, porque ${ARG} se expande a nada cuando ARG es una matriz asociativa. No puede pasar una matriz asociativa a un proceso hijo, necesita codificarlo de todos modos.

Debe definir algún protocolo para pasar argumentos entre el script principal y el script secundario. Uno común es pasar argumentos en el formulario key=value. Esto supone que el carácter = no aparece en las teclas.

También necesita averiguar cómo representar la matriz asociativa en la secuencia de comandos principal y en la secuencia de comandos secundaria. No necesitan usar la misma representación.

Un método común para representar una matriz asociativa es usar variables separadas para cada elemento, con un prefijo de denominación común. Esto requiere que el nombre de la clave solo conste de letras ASCII (en cualquier caso), dígitos y guiones bajos. Por ejemplo, en lugar de ${myarray[key]}, escriba ${myarray__key}. Si la tecla se determina en tiempo de ejecución, se necesita una ronda de expansión primero: en lugar de ${myarray[$key]}, escribir

n=myarray__${key}; echo ${!n} 

Para una asignación, uso printf -v. Tenga en cuenta el formato %s al printf para usar el valor especificado. No escriba printf -v "myarray__${key}" %s "$value" dado que eso trataría $value como un formato y realizaría la expansión printf % en él.

printf -v "myarray__${key}" %s "$value" 

Si tiene que pasar un arreglo asociativo representado como este para un proceso hijo con la representación key=value argumento, se puede utilizar para enumerar ${!myarray__*} sobre todas las variables cuyo nombre comienza con myarray__.

args=() 
for k in ${!myarray__*}; do 
    n=$k 
    args+=("$k=${!n}") 
done 

En el proceso hijo, para convertir los argumentos de la forma key=value para separar las variables con un prefijo:

for x; do 
    if [[ $x != *=* ]]; then echo 1>&2 "KEY=VALUE expected, but got $x"; exit 120; fi 
    printf -v "myarray__${x%%=*}" %s "${x#*=}" 
done 

Por cierto, ¿está seguro de que esto es lo que necesita? En lugar de llamar a un script bash desde otro script bash, es posible que desee ejecutar el script secundario en una subshell. De esta forma heredaría de todas las variables del padre.

+1

Puede usar 'printf -v' en lugar de' declare' para evitar localizar una variable en una función. Suministrar el script secundario en lugar de llamarlo también puede ser una forma de hacer que los conjuntos de padres y otras variables estén disponibles para el niño. –

+0

¡Cosas geniales! –

+0

No entiendo por qué colocaste los espacios en 'para k en' '$ {! Myarray __ *}' '; hacer' No es un comando. –

2

Puede escribir los pares clave-valor en un archivo y luego grep por tecla. Si se utiliza un patrón como

key=value 

entonces se puede egrep para ^key= que hace que esta bastante seguro.

Para "sobrescribir" un valor, simplemente añadir el nuevo valor al final del archivo y el uso tail -1 para obtener sólo el último resultado de egrep

Alternativamente, se puede poner esta información en una matriz normal utilizando key=value como valor para la matriz y luego iterador sobre la matriz para encontrar el valor.

6

Aquí es otro post/explicación de matrices asociativas en bash 3 y mayores que utilizan la expansión de parámetros: Método
https://stackoverflow.com/a/4444841

Gilles' tiene un bonito if declaración de atrapar cuestiones delimitador, desinfectar entrada excéntrica ... etc. Usa eso.

Si son un poco familiarizado con la expansión de parámetros:
http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html

para utilizar en su escenario [como se indica: el envío a la escritura]: Guión 1: sending_array.sh

# A pretend Python dictionary with bash 3 
ARRAY=("cow:moo" 
     "dinosaur:roar" 
     "bird:chirp" 
     "bash:rock") 

bash ./receive_arr.sh "${ARRAY[@]}" 

Guión 2: receive_arr.sh

argAry1=("[email protected]") 

function process_arr() { 
    declare -a hash=("${!1}") 
    for animal in "${hash[@]}"; do 
     echo "Key: ${animal%%:*}" 
     echo "Value: ${animal#*:}" 
    done 
} 

process_arr argAry1[@] 

exit 0 

Método 2, el abastecimiento a la segunda secuencia de comandos: Guión 1: sending_array.sh

source ./receive_arr.sh 
# A pretend Python dictionary with bash 3 
ARRAY=("cow:moo" 
     "dinosaur:roar" 
     "bird:chirp" 
     "bash:rock") 

process_arr ARRAY[@] 

Guión 2: receive_arr.sh

function process_arr() { 
    declare -a hash=("${!1}") 
    for animal in "${hash[@]}"; do 
     echo "Key: ${animal%%:*}" 
     echo "Value: ${animal#*:}" 
    done 
} 

Referencias:
Passing arrays as parameters in bash

2

Si no desea manejar muchas variables o claves son simplemente identificadores de variable no válidos, y su matriz está garantizada para tener menos de 256 elementos, puede abusar valores de devolución de funciones. Esta solución no requiere ninguna subcapa, ya que el valor está fácilmente disponible como una variable, ni ninguna iteración, por lo que el rendimiento se alarga. También es muy legible, casi como la versión Bash 4.

Aquí está la versión más básica:

hash_index() { 
    case $1 in 
     'foo') return 0;; 
     'bar') return 1;; 
     'baz') return 2;; 
    esac 
} 

hash_vals=("foo_val" 
      "bar_val" 
      "baz_val"); 

hash_index "foo" 
echo ${hash_vals[$?]} 

Más detalles y variantes en this answer

1

Esto resulta ser ridículamente fácil. Tuve que convertir un script bash 4 que usaba un conjunto de matrices asociativas para bash 3.Estas dos funciones de ayuda hicieron todo:

array_exp() { 
    exp=${@//[/__} 
    eval "${exp//]}" 
} 

array_clear() { 
    unset $(array_exp "echo \${!$1__*}") 
} 

Estoy atónito de que esto sea realmente así, pero esa es la belleza de la fiesta. P. ej.

((all[ping_lo] += counts[ping_lo])) 

convierte

array_exp '((all[ping_lo] += counts[ping_lo]))' 

O esta declaración de impresión:

printf "%3d" ${counts[ping_lo]} >> $return 

convierte

array_exp 'printf "%3d" ${counts[ping_lo]}' >> $return 

La única sintaxis que cambia es la limpieza. Este:

counts=() 

convierte

array_clear counts 

y listo. Podría decir fácilmente array_exp para reconocer expresiones como "=()" y manejarlas reescribiéndolas como expresiones array_clear, pero prefiero la simplicidad de las dos funciones anteriores.

+0

Esto se ve bien. Me pregunto qué hacer con declaraciones como: let 'map [$ i] ++', particularmente las comillas simples. ¿Pensamientos/comentarios? – Ray

+0

En su respuesta anterior, la instrucción eval en array_exp() está fallando cuando ejecuto el script; se está quejando de una "mala sustitución" – Ray