eval Befehl in Bash und seine typischen Verwendungen

165

Nach dem Lesen der Bash-Manpages und in Bezug auf diesen Beitrag .

Ich habe immer noch Probleme zu verstehen, was genau der evalBefehl tut und welche typischen Verwendungen dies sind. Zum Beispiel, wenn wir Folgendes tun:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one

Was genau passiert hier und wie hängen das Dollarzeichen und der Backslash mit dem Problem zusammen?

kstratis
quelle
1
Für die Aufzeichnung funktioniert der zweite Versuch. $($n)läuft $nin einer Unterschale. Es wird versucht, den 1nicht vorhandenen Befehl auszuführen .
Martin Wickman
1
@ MartinWickman Aber die Anforderung ist, echo $1irgendwann zu laufen , nicht 1. Ich glaube nicht, dass dies mit Subshells möglich ist.
Hari Menon
5
Sie sollten sich der Sicherheitsauswirkungen der Verwendungeval bewusst sein .
Bis auf weiteres angehalten.
1
@ Raze2dust: Ich glaube nicht, dass er vorgeschlagen hat, dass es mit Subshells ausgeführt werden könnte, sondern dass er erklärt, warum der fünfte Befehl, den das OP aufgelistet hat, nicht funktioniert hat.
jedwards

Antworten:

196

evalNimmt eine Zeichenfolge als Argument und wertet sie aus, als hätten Sie diese Zeichenfolge in einer Befehlszeile eingegeben. (Wenn Sie mehrere Argumente übergeben, werden diese zuerst mit Leerzeichen zwischen ihnen verbunden.)

${$n}ist ein Syntaxfehler in bash. Innerhalb der geschweiften Klammern können Sie nur einen Variablennamen mit einigen möglichen Präfixen und Suffixen haben, aber Sie können keine willkürliche Bash-Syntax haben und insbesondere können Sie keine Variablenerweiterung verwenden. Es gibt jedoch eine Möglichkeit zu sagen: "Der Wert der Variablen, deren Name in dieser Variablen enthalten ist:"

echo ${!n}
one

$(…)führt den in den Klammern angegebenen Befehl in einer Subshell aus (dh in einem separaten Prozess, der alle Einstellungen wie Variablenwerte von der aktuellen Shell erbt) und sammelt seine Ausgabe. So echo $($n)läuft $nals Shell - Befehl und zeigt seinen Ausgang. Da wird $nausgewertet 1, $($n)versucht, den Befehl auszuführen 1, der nicht existiert.

eval echo \${$n}führt die an übergebenen Parameter aus eval. Nach der Erweiterung sind die Parameter echound ${1}. Also eval echo \${$n}läuft der Befehl echo ${1}.

Beachten Sie, dass Sie die meiste Zeit doppelte Anführungszeichen für Variablensubstitutionen und Befehlssubstitutionen verwenden müssen (dh immer dann, wenn es eine gibt $) : "$foo", "$(foo)". Setzen Sie Variablen- und Befehlsersetzungen immer in doppelte Anführungszeichen , es sei denn, Sie wissen, dass Sie sie weglassen müssen. Ohne doppelte Anführungszeichen führt die Shell eine Feldaufteilung durch (dh sie teilt den Wert der Variablen oder die Ausgabe des Befehls in separate Wörter auf) und behandelt dann jedes Wort als Platzhaltermuster. Beispielsweise:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

evalwird nicht sehr oft verwendet. In einigen Shells wird am häufigsten der Wert einer Variablen ermittelt, deren Name erst zur Laufzeit bekannt ist. In Bash ist dies dank der nicht notwendig${!VAR} Syntax . evalist immer noch nützlich, wenn Sie einen längeren Befehl erstellen müssen, der Operatoren, reservierte Wörter usw. enthält.

Gilles 'SO - hör auf böse zu sein'
quelle
Wie viele "Pässe" macht die Bewertung in Bezug auf meinen obigen Kommentar?
Kstratis
@ Konos5 Welcher Kommentar? evalempfängt eine Zeichenfolge (die selbst das Ergebnis von Analyse und Auswertung sein kann) und interpretiert sie als Code-Snippet.
Gilles 'SO - hör auf böse zu sein'
Unter der Antwort von Raze2dust habe ich einen Kommentar hinterlassen. Ich neige jetzt dazu zu glauben, dass die Bewertung hauptsächlich für Dereferenzierungszwecke verwendet wird. Wenn ich eval echo \ $ {$ n} eingebe, bekomme ich eins. Wenn ich jedoch echo \ $ {$ n} eingebe, erhalte ich \ $ {1}. Ich glaube, dass dies aufgrund von Evals "Two-Pass" -Parsing geschieht. Ich frage mich jetzt, was passieren würde, wenn ich die Dereferenzierung mit einer zusätzlichen i = n-Deklaration verdreifachen müsste. In diesem Fall muss ich laut Raze2dust nur eine zusätzliche Bewertung vornehmen. Ich glaube jedoch, dass es einen besseren Weg geben sollte ... (es kann leicht unübersichtlich werden)
kstratis
@ Konos5 würde ich nicht verwenden eval eval. Ich kann mich nicht erinnern, jemals das Bedürfnis gespürt zu haben. Wenn Sie wirklich zwei evalDurchgänge benötigen , verwenden Sie eine temporäre Variable. Das Debuggen ist einfacher : eval tmp="\${$i}"; eval x="\${$tmp}".
Gilles 'SO - hör auf böse zu sein'
1
@ Konos5 "Zweimal analysiert" ist leicht irreführend. Einige Leute könnten dazu gebracht werden, dies zu glauben, da es schwierig ist, in Bash ein wörtliches String-Argument anzugeben, das vor verschiedenen Erweiterungen geschützt ist. evalNimmt einfach Code in eine Zeichenfolge und wertet ihn gemäß den üblichen Regeln aus. Technisch gesehen ist es nicht einmal richtig, da es einige Eckfälle gibt, in denen Bash das Parsen so modifiziert, dass die Argumente von eval nicht einmal erweitert werden - aber das ist ein sehr dunkler Leckerbissen, von dem ich bezweifle, dass irgendjemand etwas weiß.
Ormaaj
39

Stellen Sie sich eval einfach als "Bewertung Ihres Ausdrucks ein weiteres Mal vor der Ausführung" vor.

eval echo \${$n}wird echo $1nach der ersten Bewertungsrunde. Drei Änderungen zu beachten:

  • Das \$wurde $(Der Backslash wird benötigt, andernfalls versucht es auszuwerten ${$n}, was bedeutet, dass eine Variable namens benannt ist {$n}, was nicht erlaubt ist)
  • $n wurde ausgewertet 1
  • Die evalverschwanden

In der zweiten Runde kann es grundsätzlich echo $1direkt ausgeführt werden.

Also eval <some command>wird zuerst ausgewertet <some command>(mit hier meine ich Ersatzvariablen meinen, maskierte Zeichen durch die richtigen ersetzen usw.) und dann den resultierenden Ausdruck erneut ausführen.

evalwird verwendet, wenn Sie dynamisch Variablen erstellen oder Ausgaben von Programmen lesen möchten, die speziell für das Lesen auf diese Weise entwickelt wurden. Beispiele finden Sie unter http://mywiki.wooledge.org/BashFAQ/048 . Der Link enthält auch einige typische evalVerwendungsmöglichkeiten und die damit verbundenen Risiken.

Hari Menon
quelle
3
Als Hinweis für die erste Kugel, die ${VAR}Syntax ist erlaubt, und bevorzugt , wenn es eine Zweideutigkeit ist (nicht $VAR == $V, gefolgt von ARoder $VAR == $VAgefolgt von R). ${VAR}ist äquivalent zu $VAR. Tatsächlich ist der Variablenname $nnicht zulässig.
jedwards
2
eval eval echo \\\${\${$i}}wird eine dreifache Dereferenzierung machen. Ich bin mir nicht sicher, ob es einen einfacheren Weg gibt, das zu tun. Funktioniert auch \${$n}einwandfrei (Drucke one) auf meiner Maschine.
Hari Menon
2
@ Konos5 echo \\\${\${$i}}druckt \${${n}}. eval echo \\\${\${$i}}entspricht echo \${${n}}`` and prints $ {1} . eval eval echo \\\ $ {\ $ {$ i}} `entspricht eval echo ${1}und druckt one.
Gilles 'SO - hör auf böse zu sein'
2
@ Konos5 Denken Sie in die gleiche Richtung - Das erste ` escapes the second one, and the third `entgeht dem $danach. So wird es \${${n}}nach einer Bewertungsrunde
Hari Menon
2
@ Konos5 Von links nach rechts ist der richtige Weg, um Anführungszeichen und Backslash zu analysieren. Zuerst \\ einen Backslash ergeben. Dann \$ergibt sich ein Dollar. Und so weiter.
Gilles 'SO - hör auf böse zu sein'
25

Nach meiner Erfahrung besteht eine "typische" Verwendung von eval darin, Befehle auszuführen, die Shell-Befehle zum Festlegen von Umgebungsvariablen generieren.

Vielleicht haben Sie ein System, das eine Sammlung von Umgebungsvariablen verwendet, und Sie haben ein Skript oder Programm, das festlegt, welche festgelegt werden sollen und welche Werte sie haben. Wenn Sie ein Skript oder Programm ausführen, wird es in einem gegabelten Prozess ausgeführt, sodass alles, was es direkt mit Umgebungsvariablen tut, beim Beenden verloren geht. Dieses Skript oder Programm kann jedoch die Exportbefehle an stdout senden.

Ohne eval müssten Sie stdout in eine temporäre Datei umleiten, die temporäre Datei als Quelle bereitstellen und dann löschen. Mit eval können Sie einfach:

eval "$(script-or-program)"

Beachten Sie, dass die Anführungszeichen wichtig sind. Nehmen Sie dieses (erfundene) Beispiel:

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!
Rußschuh
quelle
Haben Sie Beispiele für gängige Tools, die dies tun? Das Tool selbst verfügt über eine Möglichkeit, eine Reihe von Shell-Befehlen zu erstellen, die an eval übergeben werden können.
Joakim Erdfelt
@Joakim Ich kenne keine OpenSource-Tools, die dies tun, aber es wurde in einigen privaten Skripten von Unternehmen verwendet, in denen ich gearbeitet habe. Ich habe gerade mit xampp wieder angefangen, diese Technik selbst anzuwenden. Apache .conf-Dateien erweitern geschriebene Umgebungsvariablen ${varname}. Ich finde es bequem, identische .conf-Dateien auf mehreren verschiedenen Servern zu verwenden, wobei nur wenige Dinge durch Umgebungsvariablen parametrisiert werden. Ich habe / opt / lampp / xampp (das Apache startet) bearbeitet, um diese Art der Auswertung mit einem Skript durchzuführen, das das System exportdurchsucht und Bash- Anweisungen ausgibt , um Variablen für die .conf-Dateien zu definieren.
Sootsnoot
@Joakim Die Alternative wäre, ein Skript zu haben, um jede der betroffenen .conf-Dateien aus einer Vorlage zu generieren, basierend auf demselben Stöbern. Eine Sache, die mir an meiner Art besser gefällt, ist, dass das Starten von Apache ohne Durchlaufen von / opt / lampp / xampp keine veralteten Ausgabeskripte verwendet, sondern nicht gestartet werden kann, da die Umgebungsvariablen zu nichts erweitert werden und ungültige Anweisungen erstellen.
Sootsnoot
@Anthony Sottile Ich sehe, dass Sie die Antwort bearbeitet haben, um Anführungszeichen um $ (Skript oder Programm) hinzuzufügen, und sagten, dass sie wichtig waren, wenn mehrere Befehle ausgeführt wurden. Können Sie bitte ein Beispiel nennen - das Folgende funktioniert gut mit durch Semikolon getrennten Befehlen im Standard von foo.sh: echo '#! / Bin / bash'> foo.sh; echo 'echo "echo -na; echo -nb; echo -n c"' >> foo.sh; chmod 755 foo.sh; eval $ (./ foo.sh). Dies erzeugt abc on stdout. Das Ausführen von ./foo.sh erzeugt: echo -na; echo -nb; echo -nc
sootsnoot
1
Ein Beispiel für ein gängiges Tool, das eval verwendet, finden Sie unter pyenv . Mit pyenv können Sie problemlos zwischen mehreren Python-Versionen wechseln. Sie fügen eval "$(pyenv init -)"in Ihre .bash_profile(oder eine ähnliche) Shell-Konfigurationsdatei ein. Das erstellt ein kleines Shell-Skript und wertet es dann in der aktuellen Shell aus.
Jerry101
10

Die eval-Anweisung weist die Shell an, die Argumente von eval als Befehl zu verwenden und sie über die Befehlszeile auszuführen. Es ist nützlich in einer Situation wie der folgenden:

Wenn Sie in Ihrem Skript einen Befehl in eine Variable definieren und später diesen Befehl verwenden möchten, sollten Sie eval verwenden:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
Nikhil Gupta
quelle
4

Update: Einige Leute sagen, man sollte niemals eval verwenden. Ich bin nicht einverstanden. Ich denke, das Risiko entsteht, wenn korrupte Eingaben an weitergegeben werden können eval. Es gibt jedoch viele häufige Situationen, in denen dies kein Risiko darstellt, und daher lohnt es sich zu wissen, wie man eval auf jeden Fall verwendet. Diese Stackoverflow-Antwort erklärt die Risiken der Bewertung und Alternativen zur Bewertung. Letztendlich ist es Sache des Benutzers, zu bestimmen, ob / wann die Bewertung sicher und effizient ist.


Mit der Bash- evalAnweisung können Sie Codezeilen ausführen, die von Ihrem Bash-Skript berechnet oder erfasst wurden.

Das vielleicht einfachste Beispiel wäre ein Bash-Programm, das ein anderes Bash-Skript als Textdatei öffnet, jede Textzeile liest und verwendet eval, um sie der Reihe nach auszuführen. Dies ist im Wesentlichen das gleiche Verhalten wie bei der Bash- sourceAnweisung, die verwendet wird, es sei denn, es ist erforderlich, eine Art Transformation (z. B. Filtern oder Ersetzen) für den Inhalt des importierten Skripts durchzuführen.

Ich habe es selten gebraucht eval, aber ich fand es nützlich, Variablen zu lesen oder zu schreiben, deren Namen in Zeichenfolgen enthalten waren, die anderen Variablen zugewiesen wurden. Zum Beispiel, um Aktionen für Variablensätze auszuführen, während der Code-Footprint klein gehalten und Redundanz vermieden wird.

evalist konzeptionell einfach. Die strenge Syntax der Bash-Sprache und die Parsing-Reihenfolge des Bash-Interpreters können jedoch nuanciert werden und evalkryptisch und schwer zu verwenden oder zu verstehen erscheinen. Hier sind die wesentlichen Punkte:

  1. Das übergebene Argument evalist ein Zeichenfolgenausdruck , der zur Laufzeit berechnet wird. evalführt das endgültige analysierte Ergebnis seines Arguments als tatsächliche Codezeile in Ihrem Skript aus.

  2. Syntax und Analysereihenfolge sind streng. Wenn das Ergebnis keine ausführbare Zeile mit Bash-Code ist, stürzt das Programm im Rahmen Ihres Skripts bei der evalAnweisung ab, wenn es versucht, Müll auszuführen.

  3. Beim Testen können Sie die evalAnweisung durch ersetzen echound sehen, was angezeigt wird. Wenn es sich im aktuellen Kontext um legitimen Code handelt, evalfunktioniert das Durchlaufen .


Die folgenden Beispiele können helfen, die Funktionsweise von eval zu verdeutlichen ...

Beispiel 1:

eval Anweisung vor 'normalem' Code ist ein NOP

$ eval a=b
$ eval echo $a
b

Im obigen Beispiel haben die ersten evalAussagen keinen Zweck und können beseitigt werden. evalist in der ersten Zeile sinnlos, da der Code keinen dynamischen Aspekt aufweist, dh er wurde bereits in die letzten Zeilen des Bash-Codes analysiert, sodass er mit einer normalen Code-Anweisung im Bash-Skript identisch wäre. Der zweite evalist ebenfalls sinnlos, da es zwar einen Parsing-Schritt gibt, der $ain sein Literal-String-Äquivalent konvertiert , aber keine Indirektion (z. B. keine Referenzierung über den String-Wert eines tatsächlichen Bash-Substantivs oder einer Bash-gehaltenen Skriptvariablen) erfolgt, sodass er sich identisch verhält als Codezeile ohne evalPräfix.



Beispiel 2:

Führen Sie die Variablenzuweisung mit Variablennamen durch, die als Zeichenfolgenwerte übergeben werden.

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

Wenn Sie echo $key=$valwären, wäre die Ausgabe:

mykey=myval

Das , das endgültige Ergebnis der String - Parsing ist, ist das, was von eval ausgeführt werden, damit das Ergebnis der Echo - Anweisung am Ende ...



Beispiel 3:

Hinzufügen von mehr Indirektion zu Beispiel 2

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

Das Obige ist etwas komplizierter als das vorherige Beispiel und stützt sich stärker auf die Parsing-Reihenfolge und die Besonderheiten von Bash. Die evalZeile wird intern grob in der folgenden Reihenfolge analysiert (beachten Sie, dass die folgenden Anweisungen Pseudocode und kein echter Code sind, nur um zu zeigen, wie die Anweisung intern in Schritte unterteilt wird, um zum Endergebnis zu gelangen) .

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval

Wenn die angenommene Analysereihenfolge nicht erklärt, was eval genug tut, beschreibt das dritte Beispiel das Parsen möglicherweise detaillierter, um zu klären, was vor sich geht.



Beispiel 4:

Ermitteln Sie, ob Vars, deren Namen in Zeichenfolgen enthalten sind, selbst Zeichenfolgenwerte enthalten.

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done

In der ersten Iteration:

varname="myvarname_a"

Bash analysiert das Argument evalund evalsieht dies zur Laufzeit buchstäblich:

eval varval=\$$myvarname_a

Der folgende Pseudocode versucht zu veranschaulichen, wie bash die obige Zeile des realen Codes interpretiert , um zu dem endgültigen Wert zu gelangen, der von ausgeführt wird eval. (Die folgenden Zeilen beschreiben den nicht genauen Bash-Code):

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)

Sobald das gesamte Parsen abgeschlossen ist, wird das Ergebnis ausgeführt, und seine Wirkung ist offensichtlich. Dies zeigt, dass an sich nichts besonders Geheimnisvolles evalist und die Komplexität im Parsen des Arguments liegt.

varval="User-provided"

Der verbleibende Code im obigen Beispiel testet einfach, ob der $ varval zugewiesene Wert null ist, und fordert den Benutzer in diesem Fall auf, einen Wert anzugeben.

klares Licht
quelle
3

Ich habe ursprünglich absichtlich nie gelernt, wie man Eval benutzt, weil die meisten Leute empfehlen werden, sich wie die Pest davon fernzuhalten. Allerdings habe ich kürzlich einen Anwendungsfall entdeckt, der mich dazu brachte, ihn nicht früher zu erkennen.

Wenn Sie Cron-Jobs haben, die Sie interaktiv zum Testen ausführen möchten, können Sie den Inhalt der Datei mit cat anzeigen und den Cron-Job kopieren und einfügen, um ihn auszuführen. Leider geht es darum, die Maus zu berühren, was in meinem Buch eine Sünde ist.

Nehmen wir an, Sie haben einen Cron-Job unter /etc/cron.d/repeatme mit dem Inhalt:

*/10 * * * * root program arg1 arg2

Sie können dies nicht als Skript mit dem gesamten Junk davor ausführen, aber wir können cut verwenden, um den gesamten Junk zu entfernen, ihn in eine Subshell zu wickeln und den String mit eval auszuführen

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

Der Befehl cut druckt nur das 6. Feld der Datei aus, das durch Leerzeichen begrenzt ist. Eval führt diesen Befehl dann aus.

Ich habe hier einen Cron-Job als Beispiel verwendet, aber das Konzept besteht darin, Text aus stdout zu formatieren und diesen Text dann auszuwerten.

Die Verwendung von eval ist in diesem Fall nicht unsicher, da wir genau wissen, was wir vorab bewerten werden.

Luke Pafford
quelle
2

Ich musste kürzlich evalerzwingen, dass mehrere Klammererweiterungen in der von mir benötigten Reihenfolge ausgewertet werden. Bash führt also mehrere Klammererweiterungen von links nach rechts durch

xargs -I_ cat _/{11..15}/{8..5}.jpg

erweitert sich auf

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

aber ich brauchte die zweite Klammererweiterung, die zuerst durchgeführt wurde und nachgab

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

Das Beste, was ich mir dazu einfallen lassen konnte, war

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

Dies funktioniert, weil die einfachen Anführungszeichen den ersten Satz von Klammern während des Parsens der evalBefehlszeile vor einer Erweiterung schützen und sie durch die von aufgerufene Unterschale erweitert werden können eval.

Es mag ein listiges Schema geben, das verschachtelte Klammererweiterungen beinhaltet, die dies in einem Schritt ermöglichen, aber wenn es welche gibt, bin ich zu alt und dumm, um es zu sehen.

flabdablet
quelle
1

Sie haben nach typischen Verwendungszwecken gefragt.

Eine häufige Beschwerde über Shell-Skripte ist, dass Sie (angeblich) nicht als Referenz übergeben können, um Werte aus Funktionen zurückzugewinnen.

Aber eigentlich über "eval" Sie können als Verweis übergeben. Der Angerufene kann eine Liste von Variablenzuweisungen zurückgeben, die vom Anrufer ausgewertet werden sollen. Es wird als Referenz übergeben, da der Aufrufer die Namen der Ergebnisvariablen angeben kann (siehe Beispiel unten). Fehlerergebnisse können Standardnamen wie errno und errstr zurückgegeben werden.

Hier ist ein Beispiel für die Übergabe als Referenz in Bash:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

Die Ausgabe sieht folgendermaßen aus:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

In dieser Textausgabe ist die Bandbreite nahezu unbegrenzt ! Und es gibt mehr Möglichkeiten, wenn mehrere Ausgabezeilen verwendet werden: z. B. könnte die erste Zeile für variable Zuweisungen verwendet werden, die zweite für einen kontinuierlichen „Gedankenstrom“, aber das würde den Rahmen dieses Beitrags sprengen.

Craig Hicks
quelle
Zu sagen, dass es "mehr Möglichkeiten" gibt, ist, gelinde gesagt, unbedeutend, trivial und überflüssig.
Dotbit
0

Ich mag die Antwort "Bewerten Sie Ihren Ausdruck ein weiteres Mal vor der Ausführung" und möchte dies anhand eines anderen Beispiels verdeutlichen.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

Die merkwürdigen Ergebnisse in Option 2 sind, dass wir 2 Parameter wie folgt übergeben hätten:

  • Erster Parameter: "value
  • Zweiter Parameter: content"

Wie ist das für kontraintuitiv? Das zusätzlicheeval wird das beheben.

Angepasst von https://stackoverflow.com/a/40646371/744133

YoYo
quelle
0

In der Frage:

who | grep $(tty | sed s:/dev/::)

gibt Fehler aus, die behaupten, dass die Dateien a und tty nicht vorhanden sind. Ich habe dies so verstanden, dass tty nicht vor der Ausführung von grep interpretiert wird, sondern dass bash tty als Parameter an grep übergeben hat, der es als Dateinamen interpretiert hat.

Es gibt auch eine Situation verschachtelter Umleitung, die von übereinstimmenden Klammern behandelt werden sollte, die einen untergeordneten Prozess angeben sollten. Bash ist jedoch primitiv ein Worttrennzeichen, das Parameter erstellt, die an ein Programm gesendet werden sollen. Daher werden Klammern nicht zuerst abgeglichen, sondern als interpretiert gesehen.

Ich wurde spezifisch mit grep und gab die Datei als Parameter an, anstatt eine Pipe zu verwenden. Ich habe auch den Basisbefehl vereinfacht und die Ausgabe eines Befehls als Datei übergeben, damit die E / A-Piping nicht verschachtelt wird:

grep $(tty | sed s:/dev/::) <(who)

funktioniert gut.

who | grep $(echo pts/3)

ist nicht wirklich erwünscht, beseitigt aber das verschachtelte Rohr und funktioniert auch gut.

Zusammenfassend scheint Bash kein verschachteltes Pipping zu mögen. Es ist wichtig zu verstehen, dass bash kein rekursiv geschriebenes New-Wave-Programm ist. Stattdessen ist bash ein altes 1,2,3-Programm, an das Funktionen angehängt wurden. Um die Abwärtskompatibilität sicherzustellen, wurde die ursprüngliche Interpretationsweise nie geändert. Wenn bash so umgeschrieben würde, dass es zuerst mit Klammern übereinstimmt, wie viele Fehler würden in wie viele bash-Programme eingeführt? Viele Programmierer lieben es, kryptisch zu sein.

Paul
quelle