Variables de bucle

16

Estoy escribiendo un script bash para usar rsync y actualizar archivos en unos 20 servidores diferentes.

Tengo la parte rsync resuelta. Lo que me está costando es revisar una lista de variables.

Mi script hasta ahora se ve así:

#!/bin/bash
SERVER1="192.xxx.xxx.2"
SERVER2="192.xxx.xxx.3"
SERVER3="192.xxx.xxx.4"
SERVER4="192.xxx.xxx.5"
SERVER5="192.xxx.xxx.6"
SERVER6="192.xxx.xxx.7"

for ((i=1; i<7; i++))
do
    echo [Server IP Address]
done

Donde [Server IP Address]debe ser el valor de la variable asociada. Entonces, cuando i = 1, debería hacer eco del valor de $ SERVER1.

He intentado varias iteraciones de esto, incluyendo

echo "$SERVER$i"    # printed the value of i
echo "SERVER$i"     # printer "SERVER" plus the value of i ex: SERVER 1 where i = 1
echo $("SERVER$i")  # produced an error SERVER1: command not found where i = 1
echo $$SERVER$i     # printed a four digit number followed by "SERVER" plus the value of i
echo \$$SERVER$i    # printed "$" plus the value of i

Ha pasado mucho tiempo desde que escribí el guión, así que sé que me falta algo. Además, estoy seguro de que estoy mezclando lo que podría hacer con C #, que he usado durante los últimos 11 años.

¿Es posible lo que intento hacer? ¿O debería poner estos valores en una matriz y recorrer la matriz? Necesito lo mismo para las direcciones IP de producción, así como los nombres de ubicación.

Todo esto es un esfuerzo para no tener que repetir un bloque de código que usaré para sincronizar los archivos en el servidor remoto.

Paul Stoner
fuente
Gracias por todas las respuestas y comentarios. Lo más probable es que adopte el enfoque de matriz.
Paul Stoner

Respuestas:

33

Usa una matriz.

#! /bin/bash
servers=( 192.xxx.xxx.2 192.xxx.xxx.3
          192.xxx.xxx.4 192.xxx.xxx.5
          192.xxx.xxx.6 192.xxx.xxx.7
)

for server in "${servers[@]}" ; do
    echo "$server"
done
choroba
fuente
66
+1; y tenga en cuenta que puede poner espacios en blanco arbitrarios antes / después / entre los elementos de la matriz, por lo que puede (si lo desea) poner un elemento por línea como en el OP.
ruakh
@ruakh: gracias, actualizado para mostrar lo que se puede hacer.
choroba
28

Como señalan las otras respuestas, una matriz es la forma más conveniente de hacer esto. Sin embargo, para completar, exactamente lo que está pidiendo es una expansión indirecta . Reescrito de la siguiente manera, su muestra también funcionará usando este método:

#!/bin/bash
SERVER1="192.xxx.xxx.2"
SERVER2="192.xxx.xxx.3"
SERVER3="192.xxx.xxx.4"
SERVER4="192.xxx.xxx.5"
SERVER5="192.xxx.xxx.6"
SERVER6="192.xxx.xxx.7"

for ((i=1; i<7; i++))
do
    servervar="SERVER$i"
    echo "${!servervar}"
done

Si está de acuerdo con solo poner la lista de direcciones IP en el forbucle, entonces también podría considerar simplemente usar algunas expansiones de llaves para iterar sobre lo que necesite:

#!/bin/bash

for server in \
192.xxx.xxx.{2..7} \
192.yyy.yyy.{42..50} \
192.zzz.zzz.254
do
    echo "$server"
done

Pero si necesita reutilizar la lista (posiblemente expandida entre corchetes), usar la lista para inicializar una matriz sería el camino a seguir:

#!/bin/bash

servers=(
192.xxx.xxx.{2..7} 
192.yyy.yyy.{42..50}
192.zzz.zzz.254 )

for server in "${servers[@]}"
do
    echo "$server"
done
Trauma digital
fuente
14

Si bien probablemente iría con una de las respuestas de la matriz yo mismo, me gustaría señalar que es posible recorrer los nombres directamente. Tu puedes hacer

for name in "${!SERVER*}"; do
    echo "${!name}"
done

o, en 4.3 y versiones posteriores, puede usar un nameref:

declare -n name
for name in "${!SERVER*}"; do
    echo "$name"
done

Hat tip ilkkachu para la solución <4.3.

kojiro
fuente
2
La expansión indirecta ( ${!var}) también funciona con versiones anteriores de Bash:foo1=aa; foo2=bbb; foox=cccc; for p in "${!foo@}"; do echo "$p = ${!p}"; done
ilkkachu
@ilkkachu gracias por la ayuda. He actualizado mi respuesta.
kojiro
6

Todos los que dijeron que debería usar una matriz son correctos, pero, como ejercicio académico, si estuviera decidido a hacerlo con variables separadas que terminen en un número (SERVER1, SERVER2, etc.), así es como lo haría eso:

for ((i=1; i<7; i++))
do
    eval echo \"\$SERVER$i\"
done
Beam Davis
fuente
evalEs difícil de usar de forma segura. Sí, esto puede funcionar, pero bash expansión indirecta es una mejor manera en general.
Peter Cordes
Hábito. Cuando comencé a escribir scripts de shell, incluso en bash, no hubo expansión indirecta. "eval" es perfectamente seguro si entiendes cómo escapar correctamente. Aún así, estoy de acuerdo con usted en que la expansión indirecta es mejor ... pero las viejas formas aún funcionan. Tampoco lo haría en este caso de todos modos. ¡Usaría una matriz!
Haz Davis el
En este caso, tenga en cuenta que no escapó de las comillas dobles, por lo que una vez evalhecho esto, no lo ha echo $SERVER1hecho echo "$SERVER1". Sí, es posible usarlo de manera segura, ¡pero es muy fácil de usar de manera insegura o no como pretendía!
Peter Cordes
Dado que un nombre de servidor no contendría espacios, de todos modos no habría importado en este caso ... pero tiene razón, las comillas dobles deben escaparse. Corregido en respuesta!
Haz Davis el
Correcto, eliminar las comillas dobles por completo (para evitar la impresión engañosa de que estaban protegiendo algo) sería la otra opción. Pero escapar de ellos también es la forma más genérica, entonces +1.
Peter Cordes