So weisen Sie Variablen in bash mit eval speicherplatzhaltige Werte zu

19

Ich möchte Variablen mit dynamisch Werte zuweisen eval. Das folgende Dummy-Beispiel funktioniert:

var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange

Wenn der Variablenwert jedoch Leerzeichen enthält, wird evalein Fehler zurückgegeben, auch wenn $var_valuezwischen Anführungszeichen gesetzt wird:

var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found

Wie kann man das umgehen?

Sébastien Clément
quelle

Antworten:

13

Nicht eval verwenden, verwenden declare

$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<
Glenn Jackman
quelle
11

Verwenden Sie nicht evaldafür; verwenden declare.

var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"

Beachten Sie, dass die Wortteilung kein Problem darstellt, da nicht nur das erste Wort, sondern alles, was auf das =folgt, als Wert von behandelt wird declare.

In bash4.3 vereinfachen benannte Referenzen dies ein wenig.

$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange

Du kannsteval Arbeit machen , aber du solltest es trotzdem nicht :) Es evalist eine schlechte Angewohnheit, sich darauf einzulassen.

$ eval "$(printf "%q=%q" "$var_name" "$var_value")"
chepner
quelle
2
Mit auf eval diese Weise ist falsch. Sie erweitern, $var_valuebevor Sie es übergeben, evalwas bedeutet, dass es als Shell-Code interpretiert wird! (versuchen Sie es zum Beispiel mit var_value="';:(){ :|:&};:'")
Stéphane Chazelas
1
Guter Punkt; Es gibt einige Zeichenfolgen, die Sie nicht sicher zuweisen können eval(was ein Grund ist, warum ich sagte, dass Sie nicht verwenden sollten eval).
Chepner
@chepner - ich glaube nicht, dass das stimmt. Vielleicht ist es das, aber zumindest nicht dieses. Parametersubstitutionen ermöglichen eine bedingte Erweiterung, so dass Sie in den meisten Fällen, glaube ich, nur sichere Werte erweitern können. Ihr Hauptproblem $var_valueist jedoch die Umkehrung der Anführungszeichen - wenn Sie einen sicheren Wert für annehmen $var_name (was wirklich eine ebenso gefährliche Annahme sein kann) , sollten Sie die doppelten Anführungszeichen der rechten Seite in einfache Anführungszeichen einschließen - nicht und umgekehrt.
mikeserv
Ich denke, ich habe das eval, using printfund sein bash-spezifisches %qFormat behoben . Dies ist immer noch keine Empfehlung eval, aber ich denke, es ist sicherer als zuvor. Die Tatsache, dass Sie sich so viel Mühe geben müssen, um es zum Laufen zu bringen, ist ein Beweis dafür, dass Sie declarestattdessen Referenzen verwenden oder benennen sollten .
Chepner
Nun, meiner Meinung nach sind benannte Referenzen das Problem. Der beste Weg, es zu benutzen, ist meiner Erfahrung nach ... set -- a bunch of args; eval "process2 $(process1 "$@")"wo process1nur zitierte Zahlen wie gedruckt werden "${1}" "${8}" "${138}". Das ist verrückt einfach - und so einfach wie '"${'$((i=$i+1))'}" 'in den meisten Fällen. Indizierte Referenzen machen es sicher, robust und schnell . Trotzdem - ich habe aufgestimmt.
mikeserv
4

Ein guter Weg, um damit zu arbeiten, evalbesteht darin, es echozu Testzwecken zu ersetzen . echound evalarbeiten genauso (wenn wir die \xErweiterung durch einige echoImplementierungen wie bash's unter bestimmten Bedingungen aufheben).

Beide Befehle verbinden ihre Argumente mit einem Leerzeichen dazwischen. Der Unterschied besteht darin, dass das Ergebnis echo angezeigt wird, während das Ergebnis als Shell-Code eval ausgewertet / interpretiert wird .

Also, um zu sehen, welcher Shell-Code

eval $(echo $var_name=$var_value)

würde bewerten, können Sie ausführen:

$ echo $(echo $var_name=$var_value)
fruit=blue orange

Das ist nicht was du willst, was du willst ist:

fruit=$var_value

Auch $(echo ...)hier macht es keinen Sinn.

Um das Obige auszugeben, würden Sie Folgendes ausführen:

$ echo "$var_name=\$var_value"
fruit=$var_value

Um es zu interpretieren, ist das einfach:

eval "$var_name=\$var_value"

Beachten Sie, dass damit auch einzelne Array-Elemente festgelegt werden können:

var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"

Wie andere bereits gesagt haben, können Sie Folgendes verwenden, wenn es Ihnen egal ist, ob Ihr Code bashspezifisch ist declare:

declare "$var_name=$var_value"

Beachten Sie jedoch, dass es einige Nebenwirkungen hat.

Der Gültigkeitsbereich der Variablen ist auf die Funktion beschränkt, in der sie ausgeführt wird. Sie können sie also beispielsweise nicht in folgenden Situationen verwenden:

setvar() {
  var_name=$1 var_value=$2
  declare "$var_name=$var_value"
}
setvar foo bar

Denn das würde deklarieren, dass eine fooVariable local to setvarso nutzlos wäre.

bash-4.2hinzugefügt , um eine -gOption für declareein erklären , global , variabel , aber das ist nicht das, was wir entweder als unser wollen setvareinen würde gesetzt globalen var im Gegensatz zu dem dem Anrufer , wenn der Anrufer eine Funktion ist, wie in:

setvar() {
  var_name=$1 var_value=$2
  declare -g "$var_name=$var_value"
}
foo() {
  local myvar
  setvar myvar 'some value'
  echo "1: $myvar"
}
foo
echo "2: $myvar"

was ausgeben würde:

1:
2: some value

Beachten Sie außerdem, dass while declareaufgerufen wird declare( bashdas Konzept wurde tatsächlich aus dem typesetBuildin der Korn-Shell entlehnt), wenn die Variable bereits festgelegt ist, declarekeine neue Variable deklariert und die Art der Zuweisung vom Typ der Variablen abhängt.

Zum Beispiel:

varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"

wird ein anderes Ergebnis erzeugen (und möglicherweise böse Nebenwirkungen haben), wenn varnamees zuvor als Skalar , Array oder assoziatives Array deklariert wurde .

Stéphane Chazelas
quelle
2
Was ist falsch daran, bash-spezifisch zu sein? OP setzt das Bash-Tag auf die Frage, damit er Bash verwendet. Es ist gut, Alternativen bereitzustellen, aber ich denke, es ist dumm, jemandem zu sagen, dass er eine Funktion einer Shell nicht verwenden soll, weil sie nicht portabel ist.
Patrick
@Patrick, den Smiley gesehen? Allerdings bedeutet die Verwendung portabler Syntax weniger Aufwand, wenn Sie Ihren Code auf ein anderes System portieren müssen, auf dem er bashnicht verfügbar ist (oder wenn Sie feststellen, dass Sie eine bessere / schnellere Shell benötigen). Die evalSyntax funktioniert in allen Bourne-ähnlichen Shells und ist POSIX, sodass auf allen Systemen eine shsolche vorhanden ist. (Das bedeutet auch, dass meine Antwort auf alle Muscheln zutrifft. Wie hier häufig vorkommt, wird früher oder später eine nicht bashspezifische Frage als Duplikat dieser Frage geschlossen.
Stéphane Chazelas
Aber was ist, wenn es $var_nameToken enthält? ... wie ;?
mikeserv
@mikeserv, dann ist es kein Variablenname. Wenn Sie nicht ihr Inhalt vertrauen können, dann müssen Sie es mit den beiden sanieren evalund declare(denken Sie an PATH, TMOUT, PS4, SECONDS...).
Stéphane Chazelas
Aber im ersten Durchgang ist es immer eine variable Erweiterung und niemals ein Variablenname bis zum zweiten. In meiner Antwort desinfiziere ich es mit einer Parametererweiterung, aber wenn Sie implizieren, dass die Desinfektion in einer Unterschale beim ersten Durchgang durchgeführt wird, könnte dies auch portabel mit dem Parameter erfolgen export. Ich folge jedoch nicht den Klammern am Ende.
mikeserv
1

Wenn Sie tun:

eval "$name=\$val"

... und $nameenthält ein ;- oder eines von mehreren anderen Tokens, die die Shell möglicherweise als Begrenzung eines einfachen Befehls interpretiert -, dem eine korrekte Shell-Syntax vorausgeht, die ausgeführt wird.

name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"

AUSGABE

hi
be careful with eval

Es kann jedoch manchmal möglich sein, die Auswertung und Ausführung solcher Anweisungen zu trennen. Zum Beispiel aliaskann ein Befehl vorab ausgewertet werden. Im folgenden Beispiel wird die Variablendefinition in einer gespeichert alias, die nur dann erfolgreich deklariert werden kann, wenn die $nmauszuwertende Variable keine Bytes enthält, die nicht mit ASCII-alphanumerischen Zeichen oder übereinstimmen _.

LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD

evalwird hier verwendet, um das Neue aliasvon einem Variablennamen aufzurufen. Es wird jedoch nur dann aufgerufen, wenn die vorherige aliasDefinition erfolgreich ist, und obwohl ich weiß, dass viele verschiedene Implementierungen viele verschiedene Arten von Werten für aliasNamen akzeptieren , bin ich noch nicht auf einen gestoßen, der einen vollständig leeren Wert akzeptiert .

Die Definition in der aliasist jedoch für _$nm, und dies soll sicherstellen, dass keine signifikanten Umgebungswerte überschrieben werden. Ich kenne keine nennenswerten Umgebungswerte, die mit a beginnen, _und es ist normalerweise eine sichere Wette für die halbprivate Deklaration.

Wenn die aliasDefinition erfolgreich ist, deklariert sie jedenfalls den Wert eines aliasNamens für $nm. Und evalruft das nur auf, aliaswenn auch nicht mit einer Zahl beginnt - sonst evalbekommt man nur ein Nullargument. Wenn also beide Bedingungen erfüllt sind eval, wird der Alias ​​aufgerufen und die im Alias ​​gespeicherte Variablendefinition erstellt. Danach wird der neue aliasWert sofort aus der Hash-Tabelle entfernt.

mikeserv
quelle
;ist in Variablennamen nicht erlaubt. Wenn Sie nicht die Kontrolle über den Inhalt von haben $name, müssen Sie ihn auch für export/ bereinigen declare. Während exportCode nicht ausgeführt wird, setzen einige Variablen wie PATH, PS4und viele davon info -f bash -n 'Bash Variables'haben gleichermaßen gefährliche Nebenwirkungen.
Stéphane Chazelas
@ StéphaneChazelas - natürlich nicht erlaubt, aber nach wie vor ist es kein Variablenname beim evalersten Durchgang - es ist eine variable Erweiterung. Wie Sie an anderer Stelle sagten, ist dies in diesem Zusammenhang sehr erlaubt. Trotzdem ist das $ PATH-Argument sehr gut - ich habe eine kleine Änderung vorgenommen und werde später einige hinzufügen.
MikeServ
@ StéphaneChazelas - besser spät als nie ...?
mikeserv
In der Praxis zsh, pdksh, mksh, yashbeschweren sich nicht auf unset 'a;b'.
Stéphane Chazelas
Du wirst es auch wollen unset -v -- ....
Stéphane Chazelas