So verschieben Sie die Variablenerweiterung

18

Ich wollte oben in meinem Skript einige Zeichenfolgen mit Variablen initialisieren, die noch nicht festgelegt wurden, z.

str1='I went to ${PLACE} and saw ${EVENT}'
str2='If you do ${ACTION} you will ${RESULT}'

und dann später auf PLACE, EVENT, ACTIONund RESULTwird eingestellt. Ich möchte dann in der Lage sein, meine Zeichenfolgen mit den erweiterten Variablen auszudrucken. Ist meine einzige Option eval? Das scheint zu funktionieren:

eval "echo ${str1}"

ist das standard Gibt es einen besseren Weg, dies zu tun? Es wäre schön, nicht zu rennen, evalwenn man bedenkt, dass die Variablen alles sein könnten.

Aaron
quelle

Antworten:

23

Mit der Art der von Ihnen angezeigten Eingabe können Sie die Shell-Erweiterung nur evalin irgendeiner Form nutzen, um Werte in eine Zeichenfolge zu ersetzen . Dies ist sicher, solange Sie den Wert von steuern str1und sicherstellen können, dass nur Variablen referenziert werden, die als sicher bekannt sind (keine vertraulichen Daten enthalten) und keine anderen nicht zitierten Shell-Sonderzeichen enthalten. Sie sollten die Zeichenfolge in doppelten Anführungszeichen oder in einem hier Dokument erweitern, auf diese Weise nur "$\`speziell sind (sie müssen durch ein vorangehen \in str1).

eval "substituted=\"$str1\""

Es wäre viel robuster, eine Funktion anstelle eines Strings zu definieren.

fill_template () {
  sentence1="I went to ${PLACE} and saw ${EVENT}"
  sentence2="If you do ${ACTION} you will ${RESULT}"
}

Setzen Sie die Variablen und rufen Sie dann die Funktion fill_templatezum Setzen der Ausgabevariablen auf.

PLACE=Sydney; EVENT=fireworks
ACTION='not learn from history'; RESULT='have to relive history'
fill_template
echo "During my holidays, $sentence1."
echo "Cicero said: \"$sentence2\"."
Gilles 'SO - hör auf böse zu sein'
quelle
2
Gute Arbeit mit einer Funktion, die die Auswertung verzögert und den expliziten Auswertungsaufruf vermeidet.
Clayton Stanley
Gute Lösung, das hat mir sehr geholfen. Vielen Dank!
Stuart
8

Da ich Ihre Meinung verstehe, glaube ich nicht, dass eine dieser Antworten richtig ist. evalist in keiner Weise notwendig, und Sie müssen Ihre Variablen auch nur zweimal auswerten.

Es ist wahr, @Gilles kommt dem sehr nahe, aber er geht nicht auf das Problem möglicherweise überschreibender Werte ein und darauf, wie sie verwendet werden sollten, wenn Sie sie mehr als einmal benötigen. Eine Vorlage sollte doch mehrmals verwendet werden, oder?

Ich denke, es ist mehr die Reihenfolge, in der Sie sie bewerten, wichtig. Folgendes berücksichtigen:

OBEN

Hier legen Sie einige Standardeinstellungen fest und bereiten das Drucken vor, wenn Sie dazu aufgefordert werden ...

#!/bin/sh
    _top_of_script_pr() ( 
        IFS="$nl" ; set -f #only split at newlines and don't expand paths
        printf %s\\n ${strings}
    ) 3<<-TEMPLATES
        ${nl=
}
        ${PLACE:="your mother's house"}
        ${EVENT:="the unspeakable."}
        ${ACTION:="heroin"}
        ${RESULT:="succeed."}
        ${strings:="
            I went to ${PLACE} and saw ${EVENT}
            If you do ${ACTION} you will ${RESULT}
        "}
    #END
    TEMPLATES

MITTE

Hier definieren Sie andere Funktionen, um Ihre Druckfunktion basierend auf deren Ergebnissen aufzurufen ...

    EVENT="Disney on Ice."
    _more_important_function() { #...some logic...
        [ $((1+one)) -ne 2 ] && ACTION="remedial mathematics"
            _top_of_script_pr
    }
    _less_important_function() { #...more logic...
        one=2
        : "${ACTION:="calligraphy"}"
        _top_of_script_pr
    }

UNTERSEITE

Sie haben jetzt alles eingerichtet. Hier können Sie Ihre Ergebnisse ausführen und abrufen.

    _less_important_function
    : "${PLACE:="the cemetery"}" 
    _more_important_function
    : "${RESULT:="regret it."}" 
    _less_important_function    

ERGEBNISSE

Ich werde gleich auf das Warum eingehen, aber das Ausführen des obigen Befehls führt zu den folgenden Ergebnissen:

_less_important_function()'s erster Lauf:

Ich bin zum Haus deiner Mutter gegangen und habe Disney on Ice gesehen.

Wenn Sie Kalligraphie machen, werden Sie Erfolg haben.

dann _more_important_function():

Ich ging zum Friedhof und sah Disney on Ice.

Wenn Sie Heilmathematik machen, werden Sie Erfolg haben.

_less_important_function() nochmal:

Ich ging zum Friedhof und sah Disney on Ice.

Wenn Sie Heilmathematik machen, werden Sie es bereuen.

WIE ES FUNKTIONIERT:

Das Hauptmerkmal hierbei ist das Konzept von. conditional ${parameter} expansion.Sie können eine Variable nur dann auf einen Wert setzen, wenn sie in der folgenden Form nicht gesetzt oder null ist:

${var_name: =desired_value}

Wenn Sie stattdessen nur eine nicht festgelegte Variable :colonfestlegen möchten , lassen Sie die Werte weg, und Nullwerte bleiben unverändert .

ON SCOPE:

Möglicherweise bemerken Sie dies im obigen Beispiel $PLACEund $RESULTwerden geändert, wenn die Einstellung über ausgeführt wird parameter expansion, obwohl _top_of_script_pr()sie bereits aufgerufen wurde, und stellen sie vermutlich ein, wenn sie ausgeführt wird. Der Grund, warum dies funktioniert, ist, dass _top_of_script_pr()es sich um eine ( subshelled )Funktion handelt - ich habe sie eingeschlossen, parensanstatt { curly braces }sie für die anderen zu verwenden. Da es in einer Subshell aufgerufen wird, ist jede Variable, die es setzt, locally scopedund wenn es zu seiner übergeordneten Shell zurückkehrt, verschwinden diese Werte.

Aber wenn _more_important_function()setzt $ACTIONes globally scopedso wirkt es _less_important_function()'szweite Auswertung von $ACTIONda _less_important_function()Sätze $ACTIONnur über${parameter:=expansion}.

:NULL

Und warum verwende ich das führende ? :colon?Nun, die manSeite sagt dir, dass : does nothing, gracefully.du siehst, parameter expansiongenau wie es sich anhört - es hängt expandsvom Wert des ${parameter}.Also, wenn wir eine Variable setzen, ${parameter:=expansion}bleiben wir beim Wert - was die Shell will Versuch, inline auszuführen. Wenn es versuchen würde zu laufen the cemetery, würde es nur einige Fehler auf Sie spucken. PLACE="${PLACE:="the cemetery"}"würde die gleichen Ergebnisse liefern, aber es ist in diesem Fall auch überflüssig und ich bevorzuge die Shell: ${did:=nothing, gracefully}.

Dies können Sie tun:

    echo ${var:=something or other}
    echo $var
something or other
something or other

HIER-DOKUMENTE

Übrigens: Die Inline-Definition einer Null- oder einer nicht gesetzten Variablen ist auch der Grund, warum Folgendes funktioniert:

    <<HEREDOC echo $yo
        ${yo=yoyo}
    HEREDOC
yoyo

Der beste Weg, sich here-document eine Datei vorzustellen, ist ein Streaming zu einem Eingabedateideskriptor. Mehr oder weniger sind sie das, aber verschiedene Schalen implementieren sie etwas anders.

In jedem Fall, wenn Sie das nicht zitieren <<LIMITER, wird es gestreamt und ausgewertet. expansion.Das Deklarieren einer Variablen in einem here-documentkann funktionieren, aber nur über expansionwelche Grenzen können Sie nur Variablen setzen, die noch nicht gesetzt sind. Dies entspricht jedoch genau Ihren Anforderungen, da Ihre Standardwerte immer festgelegt werden, wenn Sie die Funktion zum Drucken von Vorlagen aufrufen.

WARUM NICHT eval?

Nun, das Beispiel, das ich vorgestellt habe, bietet ein sicheres und effektives Mittel zum Akzeptieren. parameters.Da es den Gültigkeitsbereich behandelt, kann jede Variable innerhalb von set via ${parameter:=expansion}von außen definiert werden. Wenn Sie all dies in ein Skript namens template_pr.sh einfügen und ausführen:

 % RESULT=something_else template_pr.sh

Sie würden bekommen:

Ich bin zum Haus deiner Mutter gegangen und habe Disney on Ice gesehen

Wenn Sie Kalligraphie tun, werden Sie etwas_else

Ich ging zum Friedhof und sah Disney on Ice

Wenn Sie Heilmathematik machen, werden Sie etwas_else

Ich ging zum Friedhof und sah Disney on Ice

Wenn Sie Heilmathematik machen, werden Sie etwas_else

Dies würde nicht für die Variablen funktionieren, die im Skript buchstäblich festgelegt wurden, wie $EVENT, $ACTION,und, $one,aber ich habe diese nur so definiert, um den Unterschied zu demonstrieren.

In jedem Fall ist die Annahme unbekannter Eingaben in eine evaledAnweisung inhärent unsicher, wohingegen sie parameter expansionspeziell dafür ausgelegt ist.

mikeserv
quelle
1

Sie können Platzhalter für Zeichenfolgenvorlagen anstelle von nicht erweiterten Variablen verwenden. Dies wird ziemlich schnell chaotisch. Wenn das, was Sie tun, sehr vorlagenintensiv ist, sollten Sie eine Sprache mit einer echten Vorlagenbibliothek in Betracht ziehen.

format_template() {
    changed_str=$1

    for word in $changed_str; do
        if [[ $word == %*% ]]; then
            var="${word//\%/}"
            changed_str="${changed_str//$word/${!var}}"
        fi
    done
}

str1='I went to %PLACE% and saw %EVENT%'
PLACE="foo"
EVENT="bar"
format_template "$str1"
echo "$changed_str"

Der Nachteil des oben Gesagten ist, dass die Template-Variable ein eigenes Wort sein muss (was Sie zB nicht können "%prefix%foo"). Dies könnte mit einigen Änderungen behoben werden, oder einfach durch festes Codieren der Vorlagenvariablen, anstatt dass sie dynamisch ist.

Jordanien
quelle