Doppelte und dreifache Substitution in bash und zsh

22

Follow-up zum Hintergrundteil dieser Frage .

In bashkann ich verwende ${!FOO}für doppelte Substitution, in zsh ${(P)FOO}. In beiden Fällen eval \$$FOOfunktioniert die alte Schule (Hack-y) .

Das Klügste und Logischste für mich wäre also ${${FOO}}, ${${${FOO}}}…eine doppelte / dreifache / n-Substitution. Warum funktioniert das nicht wie erwartet?

Zweitens: Was macht der \in der evalAussage? Ich denke, es ist eine Flucht, die so etwas wie eval \$$$FOOunmöglich macht. Wie macht man eine Triple / N-Substitution mit der, die in jeder Shell funktioniert?

Profpatsch
quelle

Antworten:

15

Das \muss verwendet werden, um die Erweiterung von $$(aktuelle Prozess-ID) zu verhindern . Für die dreifache Substitution benötigen Sie eine doppelte Auswertung, sodass mehr Ausweichversuche erforderlich sind, um unerwünschte Erweiterungen in jeder Auswertung zu vermeiden:

#! /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
quelle
Dennoch: l3=l2; eval eval eval echo \\\$\\$\$$l353294also nicht genau modular.
Profpatsch
2
Warum kann ich so etwas nicht machen eval $(eval echo \$$l2)? Ich hasse Bash, es macht absolut keinen Sinn.
Profpatsch
@Profpatsch: Weitere Beispiele hinzugefügt.
Choroba
Ah, es ist n². Ich möchte nicht einmal versuchen herauszufinden, warum das so ist.
Profpatsch
2
@Profpatsch: Einfach. In jedem Schritt halbiert sich die Anzahl der Backslashes (also tatsächlich 2ⁿ).
Choroba
10
#!/bin/bash

hello=world
echo=hello

echo $echo ${!echo}
Christopher Abad
quelle
6

Angenommen, der Wert von FOOist ein gültiger Variablenname (say BAR), eval \$$FOOder Wert von wird BARin separate Wörter aufgeteilt, jedes Wort wird als Platzhaltermuster behandelt und das erste Wort des Ergebnisses wird als Befehl ausgeführt, wobei die anderen Wörter als Argumente übergeben werden. Der umgekehrte Schrägstrich vor dem Dollar führt zu einer wörtlichen Behandlung. Das Argument, das an den evalBuiltin übergeben wird, ist die Zeichenfolge mit vier Zeichen $BAR.

${${FOO}}ist ein Syntaxfehler. Es wird keine "doppelte Ersetzung" durchgeführt, da es in keiner der gängigen Shells eine solche Funktion gibt (ohnehin nicht mit dieser Syntax). In zsh ${${FOO}} ist gültig und ist eine doppelte Substitution, aber es verhält sich anders als gewünscht: Es werden zwei aufeinanderfolgende Transformationen des Werts von ausgeführt FOO, wobei beide die Identitätsumwandlung sind, sodass es sich nur um eine ausgefallene Schreibweise handelt ${FOO}.

Wenn Sie den Wert einer Variablen als eine Variable behandeln möchten, achten Sie darauf, die Dinge richtig in Anführungszeichen zu setzen. Es ist viel einfacher, wenn Sie das Ergebnis auf eine Variable setzen:

eval "value=\${$FOO}"
Gilles 'SO - hör auf böse zu sein'
quelle
1
Als Variable setzen, damit das erste Wort nicht als Kommando verwendet wird? Mann, diese Art von Logik ist schwer zu verstehen. Es ist das allgemeine Problem, das ich anscheinend mit der Bash habe.
Profpatsch
4

{ba,z}sh Lösung

Hier ist eine Funktion, die in beiden Fällen funktioniert {ba,z}sh. Ich glaube, es ist auch POSIX-konform.

Ohne verrückt nach Zitieren zu werden, können Sie es für viele Indirektionsebenen verwenden:

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

Oder wenn Sie mehr (!?!) Indirektionsebenen haben, können Sie eine Schleife verwenden.

Es warnt, wenn gegeben:

  • Null-Eingabe
  • Ein Variablenname, der nicht festgelegt ist

# 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
quelle
Eine funktionierende Cross-Shell-Lösung für mich. Hoffentlich wird "POSIX-kompatible doppelte Variablenerweiterung" in Zukunft auf diese Antwort umgeleitet.
BlueDrink9
2

Genau wie ${$foo}funktioniert nicht anstelle von ${(P)foo}in zsh, auch nicht ${${$foo}}. Sie müssen nur die einzelnen Indirektionsebenen angeben:

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

Funktioniert natürlich ${!${!foo}}nicht bash, da bashkeine verschachtelten Ersetzungen zulässig sind.

chepner
quelle
Danke, das ist gut zu wissen. Schade, dass dies nur zsh ist, so dass man es nicht wirklich in ein Skript schreiben kann (jeder hat Bash, aber es ist nicht umgekehrt).
Profpatsch
Ich vermute, dass zshes viel öfter installiert wird, als Sie vielleicht annehmen. Es kann sein, dass es nicht shwie bashoft verlinkt ist (aber nicht immer), aber es kann immer noch für Shell-Skripte verfügbar sein. Stellen Sie es sich wie Perl oder Python vor.
Chepner
2

Warum müssten Sie das tun?

Sie können dies immer in mehreren Schritten tun:

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

Oder benutze eine Funktion wie:

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

Dann:

$ 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, in zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah
Stéphane Chazelas
quelle
Huh, mit mehreren Schritten ist mir bisher nicht in den Sinn gekommen ... seltsam.
Profpatsch
Aus meiner Sicht ist es offensichtlich, warum @Profpatsch das tun möchte . Weil es der intuitivste Weg ist, dies zu tun. Es ist auch schwierig , mehrere Ersetzungen in bash zu implementieren.
Nikos Alexandris
2

POSIX-Lösung

Ohne verrückt nach Zitaten zu werden, können Sie diese Funktion für viele Indirektionsebenen verwenden:

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

Oder wenn Sie mehr (!?!) Indirektionsebenen haben, können Sie eine Schleife verwenden.

Es warnt, wenn gegeben:

  • Null-Eingabe
  • Mehr als ein Argument
  • Ein Variablenname, der nicht festgelegt ist

# 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
quelle
Gibt es einen Grund, warum Sie diese Antwort nicht mit Ihrer vorherigen kombiniert haben? Auf einen Blick sehe ich keinen Unterschied zwischen ihnen.
BlueDrink9