matriz bash con variable en el nombre

matriz bash con variable en el nombre

Agradeceré su ayuda con el siguiente problema:

Estoy intentando establecer una matriz que contenga una variable como parte del nombre de la matriz, ejemplo: Arr_$COUNTER(donde $COUNTERse cambia según un recuento de bucles)

Todas las formas posibles que he probado arrojaron un error, como "sustitución incorrecta" o "error de sintaxis cerca de un token inesperado".

Aquí está el flujo completo:

  1. Hay un archivo que contiene varias líneas. cada línea tiene 6 valores separados por espacio

    10 20 30 40 50 60  
    100 200 300 400 500 600
    
  2. El script está destinado a leer cada línea del archivo y declararlo como una matriz (con el número de línea que es la variable.

  3. como prueba, se debe imprimir cada valor y eventualmente se ejecutará otra función en cada valor.

    #!/bin/bash
    COUNTER=1
    LINES=`wc -l VALUES_FILE.txt | awk '{print $1}'`
    echo "Total number of lines "$LINES
    echo
    while [ $COUNTER -le $LINES ]
    do
    echo "Counter value is $COUNTER"
    field=`awk "NR == $COUNTER" VALUES_FILE.txt`
    echo "Field = $field"
    declare -a "arr$COUNTER=($field)"
    echo "arr$COUNTER[0] = ${arr$COUNTER[0]}"
    echo "arr$COUNTER[1] = ${arr$COUNTER[1]}"
    echo "arr$COUNTER[2] = ${arr$COUNTER[2]}"
    echo "arr$COUNTER[3] = ${arr$COUNTER[3]}"
    echo "arr$COUNTER[4] = ${arr$COUNTER[4]}"
    echo "arr$COUNTER[5] = ${arr$COUNTER[5]}"
    let COUNTER=COUNTER+1
    echo
    done
    echo "The End"
    echo
    

Aquí está el resultado:

Número total de líneas 2

El valor del contador es 1.
Campo = 10 20 30 40 50 60
./sort.sh: línea 12: arr$COUNTER[0] = ${arr$COUNTER[0]}: mala sustitución
El fin

¿Qué se debe cambiar/arreglar para que funcione correctamente?

agradecer !

Respuesta1

Algunas ideas:

  1. Una "expansión de parámetro" de un valor de variable (la parte ${...}):

    echo "arr$COUNTER[0] = ${arr$COUNTER[0]}"
    

    no trabajará. Puedes evitarlo usando eval (pero no lo recomiendo):

    eval echo "arr$COUNTER[0] = \${arr$COUNTER[0]}"
    

    Esa línea podría escribirse así:

    i="arr$COUNTER[0]"; echo "$i = ${!i}"
    

    Eso se llama indirección (¡el!) en Bash.

  2. Un problema similar ocurre con esta línea:

    declare -a "arr$COUNTER=($field)"
    

    Que debe dividirse en dos líneas y usarse eval:

    declare -a "arr$COUNTER"
    eval arr$COUNTER\=\( \$field \)
    

    Nuevamente, no recomiendo usar eval (en este caso).

  3. Mientras lee el archivo completo en la memoria del shell, también podemos usar un método más simple para colocar todas las líneas en una matriz:

    readarray -t lines <"VALUES_FILE.txt"
    

    Eso debería ser más rápido que llamar a awk para cada línea.

Un script con todo lo anterior podría ser:

#!/bin/bash
valfile="VALUES_FILE.txt"

readarray -t lines <"$valfile"             ### read all lines in.

line_count="${#lines[@]}"
echo "Total number of lines $line_count"

for ((l=0;l<$line_count;l++)); do
    echo "Counter value is $l"             ### In which line are we?
    echo "Field = ${lines[l]}"             ### kepth only to help understanding.
    k="arr$l"                              ### Build the variable arr$COUNTER
    IFS=" " read -ra $k <<<"${lines[l]}"   ### Split into an array a line.
    eval max=\${#$k[@]}                    ### How many elements arr$COUNTER has?
    #echo "field $field and k=$k max=$max" ### Un-quote to "see" inside.
    for ((j=0;j<$max;j++)); do             ### for each element in the line.
        i="$k[$j]"; echo "$i = ${!i}"      ### echo it's value.
    done
done
echo "The End"
echo

Sin embargo, aún así, AWK puede ser más rápido si pudiéramos ejecutar lo que necesita en AWK.


Se podría realizar un procesamiento similar en awk. Suponiendo que los 6 valores se usarán como IP (4 de ellos) y los otros dos son un número y una época.

Sólo un ejemplo muy simple de un script AWK:

#!/bin/sh
valfile="VALUES_FILE.txt"
awk '
NF==6 { printf ( "IP: %s.%s.%s.%s\t",$1,$2,$3,$4)
        printf ( "number: %s\t",$5+2)
        printf ( "epoch: %s\t",$6)
        printf ( "\n" )
    }
' "$valfile"

Simplemente haga una nueva pregunta con los detalles.

Respuesta2

Puede utilizar la dirección indirecta de variable, si asigna tanto el nombre como el índice a una variable:

s="arr$COUNTER[0]"
echo "arr$COUNTER[0] = ${!s}"

Respuesta3

La forma estándar de almacenar datos de una matriz multidimensional en una matriz de dimensión 1 es almacenar cada fila en un desplazamiento en la matriz.

El elemento (i,j)se ubicará en el índice, i*m + jdonde iestá el índice de fila de base cero, jel índice de columna de base cero y mel número de columnas.

Esto también hace que sea más fácil leer los datos, ya que podemos simplemente tomar su archivo de entrada, cambiar todos los espacios a nuevas líneas y usar readarray.

En la línea de comando:

$ readarray -t arr < <( tr -s ' ' '\n' <data )
$ printf '%s\n' "${arr[@]}"
10
20
30
40
50
60
100
200
300
400
500
600

Podemos calcular el número de columnas en los datos con

$ m=$( awk '{ print NF; exit }' <data )

Y el número de filas:

$ n=$( wc -l <data )

Luego podemos iterar sobre columnas y filas en un bucle doble como de costumbre:

for (( i = 0; i < n; ++i )); do
    for (( j = 0; j < m; ++j )); do
        printf '%4d' "${arr[i*m + j]}"
    done
    printf '\n'
done

Para los datos dados, esto generaría

  10  20  30  40  50  60
 100 200 300 400 500 600

Lo ideal sería utilizar un lenguaje, como Perl, Python o C, que admita matrices multidimensionales. Es decir, si realmente necesita almacenar todo el conjunto de datos en la memoria y no puede procesarlos fila por fila.

Para el procesamiento fila por fila, awksería un buen candidato para reemplazar bash(cualquierEl lenguaje sería un buen candidato para reemplazar un shell para cualquier tipo de procesamiento de datos):

awk '{ for (i = 1; i <= NF; ++i) printf("%4d", $i); printf("\n") }' data

Respuesta4

Puede generar nombres usando eval, por ejemplo,

eval declare -a '"arr'$COUNTER'=($field)"'

esencialmente citando todos los metacaracteres excepto los que desea evaluar.

Entonces... si $COUNTERes 1, tu script serviría

declare -a "arr1=($field)"

Otras lecturas:

información relacionada