Asignar a una variable de matriz bash indirectamente, por nombre de variable construido dinámicamente

Script Bash para crear matrices múltiples desde csv con columnas desconocidas.

Intento escribir un script para comparar dos archivos csv con columnas similares. Lo necesito para ubicar la columna correspondiente de la otra csv y comparar cualquier diferencia. El truco es que me gustaría que el guión sea dynamic para permitir el ingreso de cualquier cantidad de columnas y que aún así pueda funcionar. Pensé que tenía un buen plan para resolver esto, pero resulta que me estoy encontrando con errores de syntax. Aquí hay una muestra de un csv que necesito comparar.

IP address, Notes, Nmap-SSH, Nmap-SMTP, Nmap-HTTP, Nmap-HTTPS, 10.0.0.1, , open, closed, open, open, 10.0.0.2, , closed, open, closed, closed, 

Cuando leí el archivo csv, estaba planeando buscar “IF columna == abrir; luego; rellenar la matriz de esta columna con la dirección IP” Esto me habría dado 4 listas en este escenario con las direcciones IP que estaban escuchando en dicho puerto. Podría comparar eso con la configuración de mi dispositivo de seguridad para asegurarme de que esté configurado correctamente. Finalmente a la carne, esto es lo que pensé que lograría crear las matrices para que busque más tarde. Sin embargo, me encontré con un inconveniente cuando traté de usar una variable dentro de un nombre de matriz. ¿Se puede corregir mi syntax o hay una forma mejor de hacer este tipo de cosas?

 #!/bin/bash # # # This script compares config_cleaned_.txt output against ext_web_env.csv and outputs the differences # # # Read from ext_web_env.csv file and create Array # FILENAME=./tmp/ext_web_env.csv # index=0 # while read line do # How many columns are in the .csv? varEnvCol=$(echo $line | awk -F, '{print NF}') echo "columns = $varEnvCol" # While loop to create array for each column while [ $varEnvCol != 2 ] do # Checks to see if port is open; if so then add IP address to array varPortCon=$(echo $line | awk -F, -vi=$varEnvCol '{print $i}') if [ $varPortCon = "open" ] then arr$varEnvCol[$index]="$(echo $line | awk -F, '{print $1}')" # I get this error message "line29 : arr8[194]=10.0.0.194: command not found" fi echo "arrEnv$varEnvCol is: ${arr$varEnvCol[@]}" # Another error but not as important since I am using this to debug "line31: arr$varEnvCol is: ${arr$varEnvCol[@]}: bad substitution" varEnvCol=$(($varEnvCol - 1)) done index=$(($index + 1 )) done < $FILENAME 

ACTUALIZAR

También intenté usar el comando eval ya que todos los datos serán poblados por otros scripts.

pero recibo este mensaje de error:

./compare.sh: línea 41: arr8 [83] = 10.0.0.83: comando no encontrado

Aquí está mi nuevo código para este ejemplo:

  if [[ $varPortCon = *'open'* ]] then eval arr\$varEnvCol[$index]=$(echo $line | awk -F, '{print $1}') fi 

 arr$varEnvCol[$index]="$(...)" 

no funciona de la forma esperada, no se puede asignar a variables de shell indirectamente , a través de una expresión que se expande al nombre de la variable.

Su bash de solución con eval también es defectuoso – vea abajo.


tl; dr

Si usa bash 4.3 o superior:

 declare -n targetArray="arr$varEnvCol" targetArray[index]=$(echo $line | awk -F, '{print $1}') 

bash 4.2 o anterior:

 declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')" 

Advertencia : Esto funcionará en su situación particular, pero puede fallar sutilmente en otros ; sigue leyendo para obtener más información, incluida una alternativa más robusta pero engorrosa basada en la read .

La solución basada en eval mencionada por @shellter en un comentario desde-eliminado es problemática no solo por razones de seguridad (como mencionaron), sino también porque puede ser bastante complicado con respecto a las comillas ; para completar, aquí está la solución basada en eval :

 eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')' 

Vea abajo para una explicación.


Asignar a una variable de matriz bash indirectamente :

bash 4.3+ : use declare -n para crear efectivamente un alias (‘nameref’) de otra variable

Esta es de lejos la mejor opción, si está disponible:

 declare -n targetArray="arr$varEnvCol" targetArray[index]=$(echo $line | awk -F, '{print $1}') 

declare -n efectivamente le permite referirse a una variable con otro nombre (si esa variable es una matriz o no), y el nombre para crear un alias puede ser el resultado de una expresión (una cadena expandida), como se demostró.

bash 4.2- : hay varias opciones , cada una con compensaciones

NOTA: Con variables que no son de matriz , el mejor enfoque es usar printf -v . Dado que esta pregunta es sobre variables de matriz , este enfoque no se analiza más.

  • [más robusto, pero engorroso]: use read :
 IFS=$'\n' read -r -d '' "arr$varEnvCol"[index] <<<"$(echo $line | awk -F, '{print $1}')" 
  • IFS=$'\n' asegura que ese espacio en blanco inicial y final en cada línea de entrada se deja intacto.
  • -r impide la interpretación de \ chars. en la entrada.
  • -d '' asegura que se capture TODA la entrada, incluso multilínea .
    • Tenga en cuenta, sin embargo, que cualquier \n ncars final. son despojados
    • Si solo está interesado en la primera línea de entrada, omita -d ''
  • "arr$varEnvCol"[index] expande al elemento variable - array, en este caso - para asignar a; tenga en cuenta que al hacer referencia al index variables dentro de un subíndice de matriz NO es necesario el prefijo $ , porque los subíndices se evalúan en el contexto aritmético , donde el prefijo es opcional .
  • <<< - un llamado here-string - envía su argumento a stdin , donde read toma su input desde.

  • [El más simple, pero puede romperse]: use declare :

 declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')" 
  • (Esto es un poco contrario a la intuición, en el que declare significa declarar , no modificar una variable, pero funciona en bash 3.xy 4.x, con las restricciones que se indican a continuación).
  • Funciona bien FUERA DE UNA FUNCIÓN, ya sea que la matriz haya sido explícitamente declarada con declare o no.
  • Advertencia : DENTRO de una función, solo funciona con variables LOCALES - no puede hacer referencia a variables globales de shell (variables declaradas fuera de la función) desde dentro de una función de esa manera . Intentar hacerlo crea invariablemente una variable LOCAL ECLIPSING de la variable shell-global.

  • [inseguro y complicado]: use eval :

 eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')' 
  • CAVEAT: solo use eval si controla completamente el contenido de la cadena que se evalúa; eval ejecutará cualquier comando contenido en una cadena, con resultados potencialmente no deseados.
  • Entender qué referencias de variables / sustituciones de comando se expanden cuando no es trivial: el enfoque más seguro es retrasar la expansión para que sucedan cuando se ejecuta la eval lugar de la expansión inmediata que ocurre cuando los argumentos se pasan a eval .
  • Para que una statement de asignación de variable tenga éxito, el RHS (lado derecho) debe eventualmente evaluar a un único token, ya sea sin comillas sin espacio en blanco o citado (opcionalmente con espacios en blanco).
  • El ejemplo anterior usa comillas simples para retrasar la expansión; por lo tanto, la secuencia que se pasa no debe contener comillas simples directamente y, por lo tanto, se divide en varias partes con literal ' caracteres. empalmado en como \' .
  • También tenga en cuenta que el LHS (lado izquierdo) de la statement de asignación pasado a eval debe ser una cadena de comillas dobles - utilizando una cadena sin comillas con cotización selectiva de $ no va a funcionar, curiosamente:
    • OK: eval "arr$varEnvCol[index]"=...
    • FALLAS: eval arr\$varEnvCol[index]=...