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 $COUNTER
se 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:
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
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.
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:
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.
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).
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 + j
donde i
está el índice de fila de base cero, j
el índice de columna de base cero y m
el 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, awk
serí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 $COUNTER
es 1, tu script serviría
declare -a "arr1=($field)"
Otras lecturas: