Pętla przez zmienne

16

Piszę skrypt bash, aby używać rsync i aktualizować pliki na około 20 różnych serwerach.

Rozpracowałem część rsync. Mam problem z przeglądaniem listy zmiennych.

Mój skrypt do tej pory wygląda następująco:

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

Gdzie [Server IP Address]powinna być wartość powiązanej zmiennej. Więc kiedy i = 1 powinienem powtórzyć wartość $ SERVER1.

Próbowałem kilka iteracji w tym

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

Minęło dużo czasu, odkąd napisałem scenariusz, więc wiem, że czegoś mi brakuje. Poza tym jestem pewien, że mieszam to, co mogę zrobić, używając C #, którego używałem przez ostatnie 11 lat.

Czy to, co próbuję zrobić, jest w ogóle możliwe? Czy powinienem umieszczać te wartości w tablicy i zapętlać tablicę? Potrzebuję tego samego do produkcji adresów IP, a także nazw lokalizacji.

Wszystko po to, aby nie musieć powtarzać bloku kodu, którego użyję do synchronizacji plików na zdalnym serwerze.

Paul Stoner
źródło
Dziękujemy za wszystkie odpowiedzi i komentarze. Bardziej niż prawdopodobne przyjmie podejście tablicowe.
Paul Stoner

Odpowiedzi:

33

Użyj tablicy.

#! /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
źródło
6
+1; i zauważ, że możesz wstawić dowolne białe znaki przed / po / pomiędzy elementami tablicy, więc możesz (w razie potrzeby) umieścić jeden element w wierszu, jak w OP.
ruakh
@ruakh: dzięki, zaktualizowano, aby pokazać, co można zrobić.
choroba
28

Jak wskazują inne odpowiedzi, tablica jest najwygodniejszym sposobem na zrobienie tego. Jednak dla kompletności dokładnie tym, o co prosisz, jest ekspansja pośrednia . Przepisana w następujący sposób, Twoja próbka będzie również działać przy użyciu tej metody:

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

Jeśli nie masz nic przeciwko umieszczeniu listy adresów IP w forpętli, możesz również rozważyć użycie rozszerzeń nawiasów klamrowych w celu iteracji wszystkiego, czego potrzebujesz:

#!/bin/bash

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

Ale jeśli musisz ponownie użyć listy (być może rozwiniętej nawiasami klamrowymi), skorzystaj z tej listy do zainicjowania tablicy:

#!/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
Cyfrowa trauma
źródło
14

Chociaż prawdopodobnie wybrałbym jedną z odpowiedzi na tablicę, chciałbym zauważyć, że można bezpośrednio zapętlać nazwy. Możesz to zrobić

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

lub w wersji 4.3 i nowszej możesz użyć nameref:

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

Kapelusz ilkkachu do rozwiązania <4.3.

kojiro
źródło
2
Pośrednie rozszerzenie ( ${!var}) działa również ze starszymi wersjami Basha:foo1=aa; foo2=bbb; foox=cccc; for p in "${!foo@}"; do echo "$p = ${!p}"; done
ilkkachu
@ilkkachu dzięki za pomoc. Zaktualizowałem swoją odpowiedź.
kojiro
6

Każdy, kto powiedział, że powinieneś użyć tablicy, ma rację, ale - jako ćwiczenie akademickie - gdybyś był zdecydowany zrobić to z oddzielnymi zmiennymi kończącymi się liczbą (SERVER1, SERVER2 itp.), Tak właśnie zrobiłbyś to:

for ((i=1; i<7; i++))
do
    eval echo \"\$SERVER$i\"
done
Beam Davis
źródło
evaljest trudny w użyciu bezpiecznie. Tak, to może działać, ale ogólnie pośrednia ekspansja bash jest lepszym sposobem.
Peter Cordes,
Nawyk. Kiedy zacząłem pisać skrypty powłoki, nawet w bash, nie było pośredniego rozszerzenia. „eval” jest całkowicie bezpieczny, jeśli rozumiesz, jak właściwie uciec. Mimo to zgadzam się z tobą, że pośrednia ekspansja jest lepsza ... ale stare sposoby nadal działają. Zresztą i tak bym tego nie zrobił. Użyłbym tablicy!
Beam Davis,
W takim przypadku zwróć uwagę, że nie uniknąłeś podwójnych cudzysłowów, więc po zakończeniu evalmasz echo $SERVER1, nie echo "$SERVER1". Tak, możliwe do użycia w bezpieczny sposób, ale bardzo łatwy w użyciu niezgodnie lub niezgodnie z przeznaczeniem!
Peter Cordes,
Ponieważ nazwa serwera nie zawierałaby spacji, i tak nie miałoby to znaczenia w tym przypadku ... ale masz rację, podwójne cudzysłowy powinny być poprzedzone znakami ucieczki. Naprawiono w odpowiedzi!
Beam Davis,
Racja, całkowite usunięcie podwójnych cudzysłowów (aby uniknąć mylącego wrażenia, że ​​coś chronią) byłoby drugą opcją. Ale ucieczka przed nimi również jest bardziej ogólna, więc +1.
Peter Cordes,