Podwójne i potrójne podstawienie w bash i zsh

23

Kontynuacja części podstawowej tego pytania .

W bashmogę użyć ${!FOO}do podwójnej zamiany, w zsh ${(P)FOO}. W obu działa stara szkoła (hack-y) eval \$$FOO.

Najmądrzejszą i najbardziej logiczną rzeczą byłoby dla mnie ${${FOO}}, ${${${FOO}}}…zastąpienie podwójnym / potrójnym / n. Dlaczego to nie działa zgodnie z oczekiwaniami?

Po drugie: co robi \w evaloświadczeniu? Myślę, że to ucieczka, która uniemożliwia coś takiego eval \$$$FOO. Jak zrobić potrójne / n podstawienie, które działa w każdej powłoce?

Profpatsch
źródło

Odpowiedzi:

15

\Musi być stosowany w celu zapobiegania rozszerzenie $$(bieżący identyfikator procesu). W celu potrójnego podstawienia potrzebujesz podwójnej ewaluacji, więc także większej liczby ucieczek, aby uniknąć niepożądanych rozszerzeń w każdej ewaluacji:

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4
choroba
źródło
Jednak: l3=l2; eval eval eval echo \\\$\\$\$$l353294więc nie do końca modułowy.
Profpatsch,
2
Dlaczego nie mogę zrobić czegoś takiego eval $(eval echo \$$l2)? Nienawidzę bashu, to absolutnie nie ma sensu.
Profpatsch
@Profpatsch: Dodano więcej przykładów.
choroba
Ach, to n². Nie chcę nawet próbować zrozumieć, dlaczego tak jest.
Profpatsch
2
@Profpatsch: Easy. Na każdym kroku liczba połówek ukośników odwrotnych (czyli w rzeczywistości wynosi 2ⁿ).
choroba
10
#!/bin/bash

hello=world
echo=hello

echo $echo ${!echo}
Christopher Abad
źródło
6

Załóżmy, że wartość FOOjest poprawną nazwą zmiennej (powiedzmy BAR), eval \$$FOOdzieli wartość BARna osobne słowa, traktuje każde słowo jako wzór wieloznaczny i wykonuje pierwsze słowo wyniku jako polecenie, przekazując pozostałe słowa jako argumenty. Odwrotny ukośnik przed dolarem powoduje, że jest traktowany dosłownie, więc argumentem przekazanym do evalwbudowanego jest ciąg czterech znaków $BAR.

${${FOO}}jest błędem składni. Nie wykonuje „podwójnego podstawienia”, ponieważ nie ma takiej funkcji w żadnej z popularnych powłok (zresztą i tak przy tej składni). W zsh ${${FOO}} jest poprawny i jest podwójnym podstawieniem, ale zachowuje się inaczej niż chcesz: wykonuje dwie kolejne transformacje na wartości FOO, obie są transformacją tożsamości, więc jest to tylko fantazyjny sposób pisania ${FOO}.

Jeśli chcesz traktować wartość zmiennej jako zmienną, uważaj na prawidłowe cytowanie. O wiele łatwiej jest ustawić wynik na zmienną:

eval "value=\${$FOO}"
Gilles „SO- przestań być zły”
źródło
1
Ustawienie go jako zmiennej, aby pierwsze słowo nie było używane jako polecenie? Człowieku, tego rodzaju logika jest trudna do zrozumienia. Wydaje mi się, że to ogólny problem z bash.
Profpatsch,
4

{ba,z}sh rozwiązanie

Oto funkcja, która działa w obu przypadkach {ba,z}sh. Wierzę, że jest również zgodny z POSIX.

Nie wariując na cytowanie, możesz użyć go na wielu poziomach pośrednich, takich jak:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

Lub jeśli masz więcej (!?!) Poziomów pośrednich, możesz użyć pętli.

Ostrzega, gdy podano:

  • Brak danych wejściowych
  • Nazwa zmiennej, która nie jest ustawiona

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}
Tom Hale
źródło
Dla mnie działające rozwiązanie wielopłaszczyznowe. Mamy nadzieję, że „zgodność podwójnych zmiennych zgodna z POSIX” przekieruje do tej odpowiedzi w przyszłości.
BlueDrink9,
2

Podobnie jak ${$foo}nie działa w miejsce ${(P)foo}w zsh, ani nie ${${$foo}}. Musisz tylko określić każdy poziom pośredni:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

Oczywiście ${!${!foo}}nie działa bash, ponieważ bashnie pozwala na zagnieżdżone podstawienia.

chepner
źródło
Dzięki, dobrze wiedzieć. Szkoda, że ​​jest to tylko zsh, więc tak naprawdę nie można go umieścić w skrypcie (wszyscy mają bash, ale nie jest na odwrót).
Profpatsch
Podejrzewam, że zshjest instalowany znacznie częściej, niż możesz przypuszczać. Może nie być shtak bashczęsto (ale nie zawsze) powiązany , ale wciąż może być dostępny dla skryptów powłoki. Pomyśl o tym jak o Perlu lub Pythonie.
chepner
2

Dlaczego miałbyś to robić?

Zawsze możesz to zrobić w kilku krokach, takich jak:

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

Lub użyj funkcji takiej jak:

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

Następnie:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIW, w zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah
Stéphane Chazelas
źródło
Huh, użycie kilku kroków do tej pory nie przyszło mi do głowy… dziwne.
Profpatsch
Z mojego punktu widzenia jest oczywiste, dlaczego @Profpatsch chce to zrobić . Ponieważ jest to najbardziej intuicyjny sposób. Będąc trudne do zaimplementowania różnych podstawień w bash jest inna sprawa.
Nikos Alexandris
2

Rozwiązanie POSIX

Bez szaleństwa cytowania możesz użyć tej funkcji do wielu poziomów pośrednich, takich jak:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

Lub jeśli masz więcej (!?!) Poziomów pośrednich, możesz użyć pętli.

Ostrzega, gdy podano:

  • Brak danych wejściowych
  • Więcej niż jeden argument
  • Nazwa zmiennej, która nie jest ustawiona

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}
Tom Hale
źródło
Czy istnieje powód, dla którego nie połączyłeś tej odpowiedzi z poprzednią? Na pierwszy rzut oka nie widzę różnicy między nimi.
BlueDrink9,