Erzwingen, dass bash Variablen in einer aus einer Datei geladenen Zeichenfolge erweitert

87

Ich versuche herauszufinden, wie bash (force?) Variablen in einer Zeichenfolge (die aus einer Datei geladen wurde) erweitert.

Ich habe eine Datei namens "Something.txt" mit dem Inhalt:

hello $FOO world

Ich renne dann

export FOO=42
echo $(cat something.txt)

dies gibt zurück:

   hello $FOO world

$ FOO wurde nicht erweitert, obwohl die Variable festgelegt wurde. Ich kann die Datei nicht auswerten oder beschaffen - da sie versucht, sie auszuführen (sie ist nicht so ausführbar wie sie ist - ich möchte nur die Zeichenfolge mit den interpolierten Variablen).

Irgendwelche Ideen?

Michael Neale
quelle
3
Beachten Sie die Auswirkungeneval auf die Sicherheit von .
Bis auf weiteres angehalten.
Meinten Sie den Kommentar für die Antwort unten?
Michael Neale
Ich meinte den Kommentar für dich. In mehr als einer Antwort wird die Verwendung von vorgeschlagen, evalund es ist wichtig, sich der Auswirkungen bewusst zu sein.
Bis auf weiteres angehalten.
@ TennisWilliamson gotcha - Ich antworte etwas spät, aber guter Tipp. Und natürlich hatten wir in den vergangenen Jahren Dinge wie "Muschelschock", also hat Ihr Kommentar den Test der Zeit bestanden! Tipps Hut
Michael Neale
Beantwortet das deine Frage? Bash-Erweiterungsvariable in einer Variablen
LoganMzz

Antworten:

108

Ich stolperte über das, was ich für DIE Antwort auf diese Frage halte: den envsubstBefehl.

envsubst < something.txt

Falls es in Ihrer Distribution noch nicht verfügbar ist, finden Sie es in der

GNU-Paket gettext.

@ Rockallite - Ich habe ein kleines Wrapper-Skript geschrieben, um das Problem '\ $' zu lösen.

(Übrigens gibt es eine "Funktion" von envsubst, die unter https://unix.stackexchange.com/a/294400/7088 erläutert wird nur einige der Variablen in der Eingabe zu erweitern, aber ich stimme zu, dass es viel mehr ist, den Ausnahmen zu entkommen praktisch.)

Hier ist mein Skript:

#! /bin/bash
      ## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to  keep variables from
    being expanded.
  With option   -sl   '\$' keeps the back-slash.
  Default is to replace  '\$' with '$'
"

if [[ $1 = -h ]]  ;then echo -e >&2  "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then  sl='\'  ; shift ;fi

sed 's/\\\$/\${EnVsUbDolR}/g' |  EnVsUbDolR=$sl\$  envsubst  "$@"
LenW
quelle
5
+1. Dies ist die einzige Antwort, die garantiert keine Befehlsersetzungen, Zitatentfernung, Argumentverarbeitung usw. durchführt
Josh Kelley,
6
Es funktioniert jedoch nur für vollständig exportierte Shell-Vars (in Bash). zB S = '$ a $ b'; a = set; Export b = exportiert; echo $ S | envsubst; Erträge: "exportiert"
gaoithe
1
danke - ich denke nach all den Jahren sollte ich diese Antwort akzeptieren.
Michael Neale
1
Es wird jedoch kein Escapezeichen für Dollarzeichen ( $) behandelt, z. B. echo '\$USER' | envsubstwird der aktuelle Benutzername mit einem vorangestellten Schrägstrich ausgegeben, nicht $USER.
Rockallite
3
scheint dies auf einem Mac mit Homebrew-Anforderungen zu bekommenbrew link --force gettext
KeepCalmAndCarryOn
25

Viele der Antworten mit evalundecho Art der Arbeit, brechen aber bei verschiedenen Dingen ab, wie z. B. mehreren Zeilen, dem Versuch, Shell-Meta-Zeichen zu entkommen, innerhalb der Vorlage, die nicht durch Bash erweitert werden soll, usw.

Ich hatte das gleiche Problem und schrieb diese Shell-Funktion, die, soweit ich das beurteilen kann, alles richtig handhabt. Aufgrund der Befehlsersetzungsregeln von bash werden immer noch nur nachgestellte Zeilenumbrüche aus der Vorlage entfernt, aber ich habe nie festgestellt, dass dies ein Problem ist, solange alles andere intakt bleibt.

apply_shell_expansion() {
    declare file="$1"
    declare data=$(< "$file")
    declare delimiter="__apply_shell_expansion_delimiter__"
    declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
    eval "$command"
}

Sie können es beispielsweise so verwenden, mit einem parameters.cfgShell-Skript, das nur Variablen festlegt, und template.txteiner Vorlage, die diese Variablen verwendet:

. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt

In der Praxis verwende ich dies als eine Art leichtes Vorlagensystem.

wjl
quelle
4
Dies ist einer der besten Tricks, die ich bisher gefunden habe :). Basierend auf Ihrer Antwort ist es so einfach, eine Funktion zu erstellen, die eine Variable mit einer Zeichenfolge erweitert, die Verweise auf andere Variablen enthält. Ersetzen Sie einfach $ data durch $ 1 in Ihrer Funktion, und los geht's! Ich glaube, dies ist die beste Antwort auf die ursprüngliche Frage, übrigens.
Galaxie
Auch dies hat übliche evalFallstricke. Versuchen Sie, ; $(eval date)am Ende einer Zeile in der Eingabedatei hinzuzufügen, und der dateBefehl wird ausgeführt .
Anubhava
1
@anubhava Ich bin nicht sicher, was du meinst; das ist in diesem Fall eigentlich das gewünschte Expansionsverhalten. Mit anderen Worten, ja, Sie sollten in der Lage sein, damit beliebigen Shell-Code auszuführen.
wjl
21

Du kannst es versuchen

echo $(eval echo $(cat something.txt))
Pizza
quelle
8
Verwenden echo -e "$(eval "echo -e \"`<something.txt`\"")"Sie diese Option, wenn neue Zeilen beibehalten werden sollen.
Pevik
Funktioniert wie ein Zauber
Kyb
1
Aber nur wenn dein template.txt Text ist. Diese Operation ist gefährlich, da sie Befehle ausführt (auswertet), wenn dies der Fall ist.
Kyb
2
aber dies entfernte doppelte Anführungszeichen
Thomas Decaux
Schuld daran ist die Unterschale.
Ingyhere
9

Sie möchten nicht jede Zeile drucken, sondern sie auswerten , damit Bash Variablensubstitutionen durchführen kann.

FOO=42
while read; do
    eval echo "$REPLY"
done < something.txt

Siehe help evaloder das Bash Handbuch für weitere Informationen.

Todd A. Jacobs
quelle
2
evalist gefährlich ; Sie werden nicht nur $varnameerweitert (wie Sie es tun würden envsubst), sondern $(rm -rf ~)auch.
Charles Duffy
4

Ein anderer Ansatz (der icky scheint, aber ich setze ihn trotzdem hier):

Schreiben Sie den Inhalt von Something.txt in eine temporäre Datei mit einer Echo-Anweisung:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

Geben Sie es dann wieder in eine Variable ein:

RESULT=$(source temp.out)

und das $ RESULT wird alles erweitern. Aber es scheint so falsch!

Michael Neale
quelle
1
icky, aber es ist sehr einfach, einfach und funktioniert.
Kyb
3

Wenn Sie nur möchten, dass die Variablenreferenzen erweitert werden (ein Ziel, das ich für mich selbst hatte), können Sie Folgendes tun.

contents="$(cat something.txt)"
echo $(eval echo \"$contents\")

(Die maskierten Anführungszeichen um $ content sind hier der Schlüssel)

SS781
quelle
Ich habe es versucht, aber das $ () entfernt Zeilenenden in meinem Fall, wodurch die resultierende Zeichenfolge
unterbrochen wird
Ich habe die eval "echo \"$$contents\""
Bewertungslinie in
1

Folgende Lösung:

  • ermöglicht das Ersetzen definierter Variablen

  • Lässt unveränderte Variablen Platzhalter, die nicht definiert sind. Dies ist besonders nützlich bei automatisierten Bereitstellungen.

  • unterstützt das Ersetzen von Variablen in folgenden Formaten:

    ${var_NAME}

    $var_NAME

  • meldet, welche Variablen in der Umgebung nicht definiert sind, und gibt in solchen Fällen einen Fehlercode zurück



    TARGET_FILE=someFile.txt;
    ERR_CNT=0;

    for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do     
      VAR_VALUE=${!VARNAME};
      VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
      VAR_VALUE2=${!VARNAME2};

      if [ "xxx" = "xxx$VAR_VALUE2" ]; then
         echo "$VARNAME is undefined ";
         ERR_CNT=$((ERR_CNT+1));
      else
         echo "replacing $VARNAME with $VAR_VALUE2" ;
         sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE}; 
      fi      
    done

    if [ ${ERR_CNT} -gt 0 ]; then
        echo "Found $ERR_CNT undefined environment variables";
        exit 1 
    fi
Aprodan
quelle
1
  1. Wenn something.txt hat nur eine Linie, eine bashMethode, (eine kürzere Version von Michael Neale „ekligen“ Antwort ), unter Verwendung von Verfahren und Befehlsersetzung :

    FOO=42 . <(echo -e echo $(<something.txt))

    Ausgabe:

    hello 42 world

    Beachten Sie, dass dies exportnicht benötigt wird.

  2. Wenn something.txt hat eine oder mehr Linien, GNU bewertet Methode:sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
agc
quelle
1
Ich fand die sedBewertung am besten für meinen Zweck geeignet. Ich musste Skripte aus Vorlagen erstellen, deren Werte aus Variablen stammen, die in eine andere Konfigurationsdatei exportiert wurden. Es behandelt das ordnungsgemäße Escapezeichen für die Befehlserweiterung, z. B. das Einfügen \$(date)in die Vorlage, um $(date)in die Ausgabe zu gelangen . Vielen Dank!
Gadamiak
0
$ eval echo $(cat something.txt)
hello 42 world
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
ClearCrescendo
quelle
0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)
Božský Boch
quelle
1
Vielen Dank für dieses Code-Snippet, das möglicherweise nur begrenzte und sofortige Hilfe bietet. Eine richtige Erklärung würde ihren langfristigen Wert erheblich verbessern, indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und es für zukünftige Leser mit anderen, ähnlichen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, einschließlich der von Ihnen getroffenen Annahmen.
Steinbock
-1

envsubst ist eine großartige Lösung (siehe Antwort von LenW), wenn der Inhalt, den Sie ersetzen, eine "angemessene" Länge hat.

In meinem Fall musste ich den Inhalt einer Datei ersetzen, um den Variablennamen zu ersetzen. envsubsterfordert, dass der Inhalt als Umgebungsvariablen exportiert wird, und bash hat ein Problem beim Exportieren von Umgebungsvariablen, die mehr als ein Megabyte oder so sind.

awk Lösung

Verwendung der Lösung von cuonglm aus einer anderen Frage:

needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle 
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out

Diese Lösung funktioniert auch für große Dateien.

Larry K.
quelle
-3

Folgendes funktioniert: bash -c "echo \"$(cat something.txt)"\"

Eddy
quelle
Dies ist nicht nur ineffizient, sondern auch äußerst gefährlich. Es werden nicht nur sichere $foo, sondern auch unsichere Erweiterungen ausgeführt $(rm -rf ~).
Charles Duffy