Oprócz tablic asocjacyjnych istnieje kilka sposobów uzyskiwania zmiennych dynamicznych w Bash. Zwróć uwagę, że wszystkie te techniki stwarzają ryzyko, które omówiono na końcu tej odpowiedzi.
W poniższych przykładach założę, i=37
że chcesz utworzyć alias zmiennej o nazwie, var_37
której wartość początkowa to lolilol
.
Metoda 1. Wykorzystanie zmiennej „wskaźnikowej”
Możesz po prostu zapisać nazwę zmiennej w zmiennej pośredniej, podobnie jak wskaźnik C. Bash ma wtedy składnię do odczytywania zmiennej z aliasem: ${!name}
rozwija się do wartości zmiennej, której nazwa jest wartością zmiennej name
. Można to potraktować jako dwustopniowe rozszerzenie: ${!name}
rozszerza się do $var_37
, które rozszerza się do lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Niestety, nie ma odpowiednika składni do modyfikowania zmiennej z aliasem. Zamiast tego możesz wykonać zadanie za pomocą jednej z poniższych sztuczek.
1a. Przypisywanie zeval
eval
jest zła, ale jest też najprostszym i najbardziej przenośnym sposobem osiągnięcia naszego celu. Musisz ostrożnie uciec z prawej strony zadania, ponieważ zostanie ono ocenione dwukrotnie . Łatwym i systematycznym sposobem na zrobienie tego jest uprzednia ocena prawej strony (lub użycie printf %q
).
I powinieneś sprawdzić ręcznie, czy lewa strona jest poprawną nazwą zmiennej lub nazwą z indeksem (a co jeśli był evil_code #
?). Z kolei wszystkie inne poniższe metody wymuszają to automatycznie.
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
Wady:
- nie sprawdza poprawności nazwy zmiennej.
eval
jest zły.
eval
jest zły.
eval
jest zły.
1b. Przypisywanie zread
read
Wbudowane pozwala na przypisanie wartości do zmiennej której podano nazwisko, fakt, który można wykorzystać w połączeniu z tu-strings:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
IFS
Części i opcji -r
upewnij się, że wartość jest przypisana jak jest natomiast opcja -d ''
pozwala na przypisanie wartości obsługujący kilka linii. Z powodu tej ostatniej opcji polecenie wraca z niezerowym kodem zakończenia.
Zauważ, że ponieważ używamy ciągu tutaj, do wartości dodawany jest znak nowego wiersza.
Wady:
- nieco niejasne;
- zwraca z niezerowym kodem zakończenia;
- dołącza nową linię do wartości.
1c. Przypisywanie zprintf
Od wersji Bash 3.1 (wydanej w 2005 r.), printf
Funkcja wbudowana może również przypisać wynik do zmiennej, której nazwa została podana. W przeciwieństwie do poprzednich rozwiązań, po prostu działa, nie jest potrzebny dodatkowy wysiłek, aby uciec przed rzeczami, aby zapobiec pękaniu i tak dalej.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Wady:
- Mniej przenośny (ale cóż).
Metoda 2. Wykorzystanie zmiennej „referencyjnej”
Od wersji Bash 4.3 (wydanej w 2014 r.), declare
Wbudowane oprogramowanie ma opcję -n
tworzenia zmiennej, która jest „odniesieniem do nazwy” innej zmiennej, podobnie jak odwołania w C ++. Podobnie jak w metodzie 1, odwołanie przechowuje nazwę zmiennej z aliasem, ale za każdym razem, gdy uzyskuje się dostęp do odwołania (w celu odczytu lub przypisania), Bash automatycznie rozwiązuje problem pośredni.
Ponadto Bash ma szczególny i składnię bardzo mylące dla uzyskania wartości samego odniesienia, sędzia przez siebie: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Nie pozwala to uniknąć pułapek opisanych poniżej, ale przynajmniej sprawia, że składnia jest prosta.
Wady:
Ryzyka
Wszystkie te techniki aliasingu stwarzają kilka zagrożeń. Pierwszym z nich jest wykonanie dowolnego kodu za każdym razem, gdy rozwiązujesz problem pośredni (do odczytu lub do przypisania) . Rzeczywiście, zamiast nazwy zmiennej skalarnej, na przykład var_37
, równie dobrze możesz aliasować indeks tablicy, na przykład arr[42]
. Ale Bash ocenia zawartość nawiasów kwadratowych za każdym razem, gdy jest to potrzebne, więc aliasowanie arr[$(do_evil)]
będzie miało nieoczekiwane efekty… W konsekwencji, używaj tych technik tylko wtedy, gdy kontrolujesz pochodzenie aliasu .
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
Drugie ryzyko to tworzenie aliasu cyklicznego. Ponieważ zmienne Bash są identyfikowane na podstawie nazwy, a nie zakresu, możesz nieumyślnie utworzyć alias dla siebie (myśląc, że utworzy alias zmiennej z otaczającego zakresu). Może się to zdarzyć w szczególności w przypadku używania wspólnych nazw zmiennych (takich jak var
). W konsekwencji tych technik należy używać tylko wtedy, gdy kontrolujesz nazwę zmiennej z aliasem .
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
Źródło: