Loop através de variáveis

16

Estou escrevendo um script bash para usar o rsync e atualizar arquivos em cerca de 20 servidores diferentes.

Eu tenho a parte do rsync descoberta. O que eu estou tendo problemas é através de uma lista de variáveis.

Até agora, meu script é assim:

#!/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

Onde [Server IP Address]deve ser o valor da variável associada. Então, quando i = 1, devo ecoar o valor de $ SERVER1.

Eu tentei várias iterações disso, incluindo

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

Faz muito tempo desde que eu escrevi o script, então sei que estou perdendo alguma coisa. Além disso, tenho certeza de que estou misturando o que poderia fazer usando C #, que usei nos últimos 11 anos.

O que estou tentando fazer é possível? Ou devo colocar esses valores em uma matriz e fazer um loop pela matriz? Preciso fazer o mesmo para endereços IP de produção e nomes de locais.

Tudo isso é um esforço para não precisar repetir um bloco de código que usarei para sincronizar os arquivos no servidor remoto.

Paul Stoner
fonte
Obrigado por todas as respostas e comentários. Provavelmente, adotarei a abordagem de matriz.
Paul Stoner

Respostas:

33

Use uma 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
fonte
6
+1; e observe que você pode colocar um espaço em branco arbitrário antes / depois / entre os elementos da matriz, para que você possa (se desejar) colocar um elemento por linha como no OP.
Ruakh 31/07
@ruakh: obrigado, atualizado para mostrar o que pode ser feito.
choroba 31/07
28

Como as outras respostas apontam, uma matriz é a maneira mais conveniente de fazer isso. No entanto, para ser completo, o que você está pedindo é a expansão indireta . Reescrito da seguinte forma, sua amostra também 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

Se você concorda em colocar a lista de endereços IP no forloop, considere também o uso de algumas expansões de chaves para iterar o que você precisar:

#!/bin/bash

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

Mas se você precisar reutilizar a lista (possivelmente entre chaves), usar a lista para inicializar uma matriz seria o caminho 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
fonte
14

Embora eu provavelmente deva responder uma das respostas da matriz, gostaria de salientar que é possível repetir nomes diretamente. Você pode fazer

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

ou, nas versões 4.3 e superior, você pode usar um nameref:

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

Gorro de ilkkachu para a solução <4,3.

kojiro
fonte
2
Expansão indireta ( ${!var}) funciona com versões mais antigas do Bash também:foo1=aa; foo2=bbb; foox=cccc; for p in "${!foo@}"; do echo "$p = ${!p}"; done
ilkkachu
@ilkkachu obrigado pela assistência. Eu atualizei minha resposta.
Kojiro # 01
6

Todos que disseram que você deveria usar uma matriz estão corretos, mas - como exercício acadêmico - se você estivesse determinado a fazê-lo com variáveis ​​separadas que terminam em um número (SERVER1, SERVER2, etc.), é assim que você faria isto:

for ((i=1; i<7; i++))
do
    eval echo \"\$SERVER$i\"
done
Beam Davis
fonte
evalé difícil de usar com segurança. Sim, isso pode funcionar, mas bash expansão indireta é uma maneira melhor em geral.
Peter Cordes
Hábito. Quando comecei a escrever scripts de shell, mesmo no bash, não havia expansão indireta. "eval" é perfeitamente seguro se você entender como escapar corretamente. Ainda assim, concordo com você que a expansão indireta é melhor ... mas os métodos antigos ainda funcionam. Eu também não faria neste caso. Eu usaria uma matriz!
Davis
Nesse caso, observe que você não escapou das aspas duplas; portanto, depois que evalterminar, você echo $SERVER1não conseguiu echo "$SERVER1". Sim, é possível usar com segurança, mas é muito fácil de usar de maneira insegura ou não, como você pretendia!
Peter Cordes
Como um nome de servidor não conteria espaços, nesse caso não teria importância ... mas você está certo, as aspas duplas devem ser escapadas. Corrigido em resposta!
Davis
Certo, remover completamente as aspas duplas (para evitar a impressão enganosa de que estavam protegendo alguma coisa) seria a outra opção. Mas escapar deles também é a maneira mais genérica, então +1.
22818 Peter Cordes