Зацикливание переменных

16

Я пишу bash-скрипт для использования rsync и обновления файлов примерно на 20 разных серверах.

Я разобрался с частью Rsync. У меня проблемы с просмотром списка переменных.

Мой сценарий до сих пор выглядит так:

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

Где [Server IP Address]должно быть значение связанной переменной. Поэтому, когда я = 1, я должен повторить значение $ SERVER1.

Я пробовал несколько итераций этого, в том числе

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

Прошло много времени с тех пор, как я написал сценарий, поэтому я знаю, что что-то упустил. Кроме того, я уверен, что я смешиваю то, что я мог сделать, используя C #, который я использовал в течение последних 11 лет.

Возможно ли то, что я пытаюсь сделать? Или я должен положить эти значения в массиве и цикл по массиву? Мне нужно то же самое для производственных IP-адресов, а также имен местоположений.

Это все для того, чтобы не повторять блок кода, который я буду использовать для синхронизации файлов на удаленном сервере.

Пол Стоунер
источник
Спасибо за все ответы и комментарии. Я буду более чем вероятно использовать подход массива.
Пол Стоунер

Ответы:

33

Используйте массив.

#! /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
источник
6
+1; и обратите внимание, что вы можете поместить произвольный пробел до / после / между элементами массива, так что вы можете (при желании) поместить один элемент в строку, как в OP.
Руах
@ruakh: спасибо, обновлено, чтобы показать, что можно сделать.
Чороба
28

Как указывают другие ответы, массив является наиболее удобным способом сделать это. Однако, для полноты, именно то, о чем вы просите, - это косвенное расширение . Переписав, ваш образец также будет работать с использованием этого метода:

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

Если вы согласны с тем, что поместили список IP-адресов в forцикл, вы можете также рассмотреть возможность использования некоторых расширений скобок для перебора всего, что вам нужно:

#!/bin/bash

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

Но если вам нужно повторно использовать список (возможно, с расширенными скобками), то использование этого списка для инициализации массива будет правильным способом:

#!/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
Цифровая травма
источник
14

Хотя я, вероятно, сам пойду с одним из ответов в массиве, я хотел бы отметить, что можно зацикливать имена напрямую. Ты можешь сделать

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

или, в 4.3 и выше, вы можете использовать nameref:

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

Наконечник шляпы ilkkachu для решения <4.3.

Кодзиро
источник
2
Косвенное расширение ( ${!var}) также работает со старыми версиями Bash:foo1=aa; foo2=bbb; foox=cccc; for p in "${!foo@}"; do echo "$p = ${!p}"; done
ilkkachu
@ilkkachu спасибо за помощь. Я обновил свой ответ.
Кодзиро
6

Каждый, кто сказал, что вы должны использовать массив, является правильным, но - в качестве академического упражнения - если вы решили сделать это с отдельными переменными, заканчивающимися на число (SERVER1, SERVER2 и т. Д.), Это то, что вы должны делать Это:

for ((i=1; i<7; i++))
do
    eval echo \"\$SERVER$i\"
done
Бим Дэвис
источник
evalтрудно безопасно использовать. Да, это может работать, но косвенное расширение bash является лучшим способом в целом.
Питер Кордес
Привычка. Когда я начал писать сценарии оболочки, даже в bash, не было никакого косвенного расширения. «eval» совершенно безопасен, если вы понимаете, как правильно убежать. Тем не менее, я согласен с вами, что косвенное расширение лучше ... но старые способы все еще работают. В любом случае, я бы этого не сделал. Я бы использовал массив!
Луч Дэвис
В этом случае обратите внимание, что вы не избежали двойных кавычек, так что после того, evalкак сделано, у вас echo $SERVER1нет echo "$SERVER1". Да, можно использовать безопасно, но очень просто или небезопасно, как вы и предполагали!
Питер Кордес
Поскольку имя сервера не должно содержать пробелов, оно в любом случае не имело бы значения ... но вы правы, двойные кавычки должны быть экранированы. Исправлено в ответе!
Луч Дэвис
Правильно, удаление двойных кавычек полностью (чтобы избежать ложного впечатления, что они что-то защищают) было бы другим вариантом. Но избегать их тоже более общий путь, так что +1.
Питер Кордес