Bash-Vorlagen: Wie erstelle ich Konfigurationsdateien aus Vorlagen mit Bash?


134

Ich schreibe ein Skript, um das Erstellen von Konfigurationsdateien für Apache und PHP für meinen eigenen Webserver zu automatisieren. Ich möchte keine GUIs wie CPanel oder ISPConfig verwenden.

Ich habe einige Vorlagen für Apache- und PHP-Konfigurationsdateien. Das Bash-Skript muss Vorlagen lesen, Variablen ersetzen und analysierte Vorlagen in einem Ordner ausgeben. Was ist der beste Weg das zu tun? Ich kann mir verschiedene Möglichkeiten vorstellen. Welches ist das beste oder gibt es vielleicht bessere Möglichkeiten, das zu tun? Ich möchte das in reinem Bash machen (es ist zum Beispiel in PHP einfach)

1) Wie ersetze ich $ {} Platzhalter in einer Textdatei?

template.txt:

the number is ${i}
the word is ${word}

script.sh:

#!/bin/sh

#set variables
i=1
word="dog"
#read in template one line at the time, and replace variables
#(more natural (and efficient) way, thanks to Jonathan Leffler)
while read line
do
    eval echo "$line"
done < "./template.txt"

Übrigens, wie leite ich die Ausgabe hier in eine externe Datei um? Muss ich etwas entkommen, wenn Variablen beispielsweise Anführungszeichen enthalten?

2) Verwenden von cat & sed zum Ersetzen jeder Variablen durch ihren Wert:

Gegeben template.txt:

The number is ${i}
The word is ${word}

Befehl:

cat template.txt | sed -e "s/\${i}/1/" | sed -e "s/\${word}/dog/"

Scheint mir schlecht zu sein, weil ich vielen verschiedenen Symbolen entkommen muss und bei vielen Variablen die Zeile zu lang ist.

Können Sie sich eine andere elegante und sichere Lösung vorstellen?


Antworten:


61

Sie können dies verwenden:

perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < template.txt

um alle ${...}Zeichenfolgen durch entsprechende Umgebungsvariablen zu ersetzen (vergessen Sie nicht, sie zu exportieren, bevor Sie dieses Skript ausführen).

Für Pure Bash sollte dies funktionieren (vorausgesetzt, Variablen enthalten keine $ {...} Strings):

#!/bin/bash
while read -r line ; do
    while [[ "$line" =~ (\$\{[a-zA-Z_][a-zA-Z_0-9]*\}) ]] ; do
        LHS=${BASH_REMATCH[1]}
        RHS="$(eval echo "\"$LHS\"")"
        line=${line//$LHS/$RHS}
    done
    echo "$line"
done

. Lösung, die nicht hängt, wenn RHS auf eine Variable verweist, die auf sich selbst verweist:

#!/bin/bash
line="$(cat; echo -n a)"
end_offset=${#line}
while [[ "${line:0:$end_offset}" =~ (.*)(\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})(.*) ]] ; do
    PRE="${BASH_REMATCH[1]}"
    POST="${BASH_REMATCH[4]}${line:$end_offset:${#line}}"
    VARNAME="${BASH_REMATCH[3]}"
    eval 'VARVAL="$'$VARNAME'"'
    line="$PRE$VARVAL$POST"
    end_offset=${#PRE}
done
echo -n "${line:0:-1}"

WARNUNG : Ich kenne keine Möglichkeit, Eingaben mit NULs in Bash korrekt zu verarbeiten oder die Anzahl der nachgestellten Zeilenumbrüche beizubehalten. Die letzte Variante wird so dargestellt, wie sie ist, weil Shells die binäre Eingabe „lieben“:

  1. read interpretiert Backslashes.
  2. read -r interpretiert keine Backslashes, löscht jedoch die letzte Zeile, wenn sie nicht mit einer neuen Zeile endet.
  3. "$(…)"wird so viele nachfolgende Zeilenumbrüche entfernen, wie vorhanden sind, also beende ich mit ; echo -n aund verwende echo -n "${line:0:-1}": Dies löscht das letzte Zeichen (das ist a) und behält so viele nachfolgende Zeilenumbrüche bei, wie in der Eingabe vorhanden waren (einschließlich Nein).

3
Ich würde in der Bash-Version zu wechseln, [^}]um [A-Za-Z_][A-Za-z0-9_]zu verhindern, dass die Shell über die strikte Substitution hinausgeht (z. B. wenn sie versucht, sie zu verarbeiten ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen

2
@FractalizeR Möglicherweise möchten Sie $&die Perl-Lösung ändern in "": Erstens bleibt sie ${...}unberührt, wenn sie nicht ersetzt werden kann, und zweitens wird sie durch eine leere Zeichenfolge ersetzt.
ZyX

5
HINWEIS: Anscheinend gab es eine Änderung von Bash 3.1 auf 3.2 (und höher), bei der die einfachen Anführungszeichen um den regulären Ausdruck - den Inhalt des regulären Ausdrucks als Zeichenfolgenliteral behandeln. Der obige reguläre Ausdruck sollte also ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Blue Waters

2
whileVerwenden Sie die Option, damit die Schleife die letzte Zeile liest, auch wenn sie nicht durch eine neue Zeile abgeschlossen ist while read -r line || [[ -n $line ]]; do. Darüber hinaus entfernt Ihr readBefehl führende und nachfolgende Leerzeichen aus jeder Zeile. Um dies zu vermeiden, verwenden Siewhile IFS= read -r line || [[ -n $line ]]; do
mklement0

2
Nur um eine Einschränkung für diejenigen zu beachten, die nach einer umfassenden Lösung suchen: Mit diesen ansonsten praktischen Lösungen können Sie variable Referenzen nicht selektiv vor Erweiterungen schützen (z. B. indem \ Sie sie umgehen).
mklement0

138

Versuchen envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF

11
Nur als Referenz, envsubstist bei Verwendung eines Heredocs nicht erforderlich, da bash den Heredoc als wörtliche Zeichenfolge in doppelten Anführungszeichen behandelt und bereits darin enthaltene Variablen interpoliert. Es ist jedoch eine gute Wahl, wenn Sie die Vorlage aus einer anderen Datei lesen möchten. Ein guter Ersatz für die viel umständlicheren m4.
Beporter

2
Ich war sehr angenehm überrascht, von diesem Befehl zu erfahren. Ich habe versucht, die Funktionalität von envsubst manuell ohne Erfolg zu verbessern. Danke Yottatsa!
Tim Stewart

4
Hinweis: envsubstist ein GNU-Dienstprogramm gettext und eigentlich gar nicht so robust (da gettext zum Lokalisieren menschlicher Nachrichten gedacht ist). Am wichtigsten ist, dass Backslash-Escape-$ {VAR} -Substitutionen nicht erkannt werden (Sie können also keine Vorlage haben, die zur Laufzeit $ VAR-Substitutionen verwendet, z. B. ein Shell-Skript oder eine Nginx-Conf-Datei). In meiner Antwort finden Sie eine Lösung für Backslash-Escapezeichen.
Stuart P. Bentley

4
@beporter Wenn Sie in diesem Fall diese Vorlage aus irgendeinem Grund an envsubst übergeben möchten, möchten Sie eine Vorlage verwenden <<"EOF", die keine Variablen interpoliert (Anführungszeichen in Anführungszeichen sind wie einfache Anführungszeichen von Heredocs).
Stuart P. Bentley

2
Ich habe es benutzt wie: cat template.txt | envsubst
truthadjustr

47

envsubst war neu für mich. Fantastisch.

Für die Aufzeichnung ist die Verwendung eines Heredoc eine großartige Möglichkeit, eine Conf-Datei zu erstellen.

STATUS_URI="/hows-it-goin";  MONITOR_IP="10.10.2.15";

cat >/etc/apache2/conf.d/mod_status.conf <<EOF
<Location ${STATUS_URI}>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from ${MONITOR_IP}
</Location>
EOF

1
Ich bevorzuge dies besser als envsubstweil es mich vor dem zusätzlichen apt-get install gettext-basein meinem Dockerfile gerettet hat
truthadjustr

Die Shell als vorlagenähnliches Skript jedoch ohne Installation einer externen Bibliothek und ohne Stress durch den Umgang mit kniffligen Ausdrücken.
郷 木 郷

35

Ich bin mit der Verwendung von sed einverstanden: Es ist das beste Tool zum Suchen / Ersetzen. Hier ist mein Ansatz:

$ cat template.txt
the number is ${i}
the dog's name is ${name}

$ cat replace.sed
s/${i}/5/
s/${name}/Fido/

$ sed -f replace.sed template.txt > out.txt

$ cat out.txt
the number is 5
the dog's name is Fido

1
Dies erfordert eine temporäre Datei für die Ersetzungszeichenfolge, oder? Gibt es eine Möglichkeit, dies ohne temporäre Dateien zu tun?
Vladislav Rastrusny

@FractalizeR: Einige Versionen von sed verfügen über eine -iOption (Dateien an Ort und Stelle bearbeiten), die der Perl- Option ähnelt . Überprüfen Sie die Manpage für Ihre sed .
Chris Johnsen

@FractalizeR Ja, sed -i ersetzt Inline. Wenn Sie mit Tcl (einer anderen Skriptsprache) vertraut sind, lesen Sie
Hai Vu

Ich habe die replace.sed aus einer Eigenschaftendatei mit dem folgenden sed-Befehl erstellt: sed -es / ^ / s \ / $ {/ g '-es / = /} \ // g' -es / $ / \ // g 'the.properties> replace.sed
Jaap D

Der Code von @hai vu erstellt ein sed-Programm und übergibt dieses Programm unter Verwendung des sed-f-Flags. Wenn Sie möchten, können Sie stattdessen jede Zeile des sed-Programms mit den -e-Flags in sed übergeben. FWIW Ich mag die Idee, sed als Vorlage zu verwenden.
Andrew Allbright

23

Ich denke, eval funktioniert wirklich gut. Es verarbeitet Vorlagen mit Zeilenumbrüchen, Leerzeichen und allerlei Bash-Dingen. Wenn Sie die volle Kontrolle über die Vorlagen selbst haben, natürlich:

$ cat template.txt
variable1 = ${variable1}
variable2 = $variable2
my-ip = \"$(curl -s ifconfig.me)\"

$ echo $variable1
AAA
$ echo $variable2
BBB
$ eval "echo \"$(<template.txt)\"" 2> /dev/null
variable1 = AAA
variable2 = BBB
my-ip = "11.22.33.44"

Diese Methode sollte natürlich mit Vorsicht angewendet werden, da eval beliebigen Code ausführen kann. Dies als root auszuführen kommt so gut wie nicht in Frage. Anführungszeichen in der Vorlage müssen maskiert werden, sonst werden sie von gegesseneval .

Sie können hier Dokumente verwenden , wenn Sie es vorziehen , catzuecho

$ eval "cat <<< \"$(<template.txt)\"" 2> /dev/null

@plockc hat eine Lösung gefunden, die das Problem des Bash-Zitats vermeidet:

$ eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

Bearbeiten: Teil über das Ausführen als Root mit sudo entfernt ...

Bearbeiten: Kommentar hinzugefügt, wie Anführungszeichen maskiert werden müssen, Plockc-Lösung zum Mix hinzugefügt!


Dadurch werden Anführungszeichen in Ihrer Vorlage entfernt und in einfachen Anführungszeichen nicht ersetzt. Abhängig von Ihrem Vorlagenformat kann dies zu subtilen Fehlern führen. Dies gilt jedoch wahrscheinlich für jede Bash-basierte Vorlagenmethode.
Alex B

IMHO Bash-basierte Vorlagen sind Wahnsinn, da Sie ein Bash-Programmierer sein müssen, um zu verstehen, was Ihre Vorlage tut! Aber danke für den Kommentar!
Mogsie

@AlexB: Dieser Ansatz wird zwischen einfachen Anführungszeichen ersetzen, da sie String nur wörtliche Zeichen innerhalb der umschließenden doppelten Anführungszeichen sind eher als String - Trennzeichen , wenn der evaled echo / catBefehle Prozesse sie; versuche es eval "echo \"'\$HOME'\"".
mklement0

21

Ich habe eine Bash-Lösung wie Mogsie, aber mit Heredoc anstelle von Herestring, damit Sie nicht doppelten Anführungszeichen entkommen

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

4
Diese Lösung unterstützt die Erweiterung der Bash-Parameter in der Vorlage. Meine Favoriten sind erforderliche Parameter mit ${param:?}und Verschachtelung von Text um optionale Parameter. Beispiel: ${DELAY:+<delay>$DELAY</delay>}Wird zu nichts erweitert, wenn DELAY undefiniert ist, und <delay> 17 </ Delay>, wenn DELAY = 17 ist.
Eric Bolinger

2
Oh! Und der EOF-Begrenzer kann eine dynamische Zeichenfolge wie die PID verwenden _EOF_$$.
Eric Bolinger

1
@ mklement0 Eine Problemumgehung für nachfolgende Zeilenumbrüche besteht darin, eine Erweiterung wie z. B. eine leere Variable zu verwenden $trailing_newlineoder $NL5diese als 5 Zeilenumbrüche zu erweitern.
Xebeche

@xebeche: Ja, platzieren , was schlagen Sie ganz am Ende nach innentemplate.txt , um funktionieren würde Hinterzeilenumbrüche zu bewahren.
mklement0

1
Eine elegante Lösung, aber beachten Sie, dass durch die Befehlsersetzung nachfolgende Zeilenumbrüche aus der Eingabedatei entfernt werden, obwohl dies normalerweise kein Problem darstellt. Ein weiterer Rand Fall: Durch die Verwendung von eval, wenn template.txtenthält EOFauf einer eigenen Zeile, wird es vorzeitig beenden , die hier-doc und damit den Befehl brechen. (Spitze des Hutes zu @xebeche).
mklement0

18

Bearbeiten 6. Januar 2017

Ich musste doppelte Anführungszeichen in meiner Konfigurationsdatei behalten, damit doppelte Anführungszeichen mit sed helfen:

render_template() {
  eval "echo \"$(sed 's/\"/\\\\"/g' $1)\""
}

Ich kann mir nicht vorstellen, neue Zeilen zu verfolgen, aber dazwischen werden leere Zeilen beibehalten.


Obwohl es ein altes Thema ist, fand ich IMO hier eine elegantere Lösung: http://pempek.net/articles/2013/07/08/bash-sh-as-template-engine/

#!/bin/sh

# render a template configuration file
# expand variables + preserve formatting
render_template() {
  eval "echo \"$(cat $1)\""
}

user="Gregory"
render_template /path/to/template.txt > path/to/configuration_file

Alle Credits an Grégory Pakosz .


Dadurch werden doppelte Anführungszeichen aus der Eingabe entfernt und, wenn die Eingabedatei mehrere nachgestellte Zeilenumbrüche enthält, durch ein einzelnes ersetzt.
mklement0

2
Ich brauchte zwei Backslashes weniger, damit es funktioniert, dh eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau

Leider können Sie mit diesem Ansatz keine PHP-Dateien (die sie enthalten $variables) vorlegen .
IStranger

10

Anstatt das Rad neu zu erfinden, gehen Sie mit envsubst Kann in fast jedem Szenario verwendet werden, z. B. zum Konfigurationsdateien aus Umgebungsvariablen in Docker-Containern.

Wenn Sie auf einem Mac sicherstellen, dass Sie Homebrew haben, verknüpfen Sie es mit gettext:

brew install gettext
brew link --force gettext

./template.cfg

# We put env variables into placeholders here
this_variable_1 = ${SOME_VARIABLE_1}
this_variable_2 = ${SOME_VARIABLE_2}

./.env:

SOME_VARIABLE_1=value_1
SOME_VARIABLE_2=value_2

./configure.sh

#!/bin/bash
cat template.cfg | envsubst > whatever.cfg

Jetzt benutze es einfach:

# make script executable
chmod +x ./configure.sh
# source your variables
. .env
# export your variables
# In practice you may not have to manually export variables 
# if your solution depends on tools that utilise .env file 
# automatically like pipenv etc. 
export SOME_VARIABLE_1 SOME_VARIABLE_2
# Create your config file
./configure.sh

Diese Aufrufsequenz envsubstfunktioniert tatsächlich.
Alex

Für alle anderen, die suchen, envsubstfunktioniert nicht unter MacOS, müssen Sie es mit Homebrew installieren : brew install gettext.
Matt

9

Eine längere, aber robustere Version der akzeptierten Antwort:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})?;substr($1,0,int(length($1)/2)).($2&&length($1)%2?$2:$ENV{$3||$4});eg' template.txt

Dies erweitert alle Instanzen von $VAR oder ${VAR} auf ihre Umgebungswerte (oder, wenn sie nicht definiert sind, die leere Zeichenfolge).

Es entgeht Backslashes ordnungsgemäß und akzeptiert ein $ mit Backslash-Escape, um die Substitution zu verhindern (im Gegensatz zu envsubst, das dies, wie sich herausstellt, nicht tut ).

Also, wenn Ihre Umgebung ist:

FOO=bar
BAZ=kenny
TARGET=backslashes
NOPE=engi

und Ihre Vorlage ist:

Two ${TARGET} walk into a \\$FOO. \\\\
\\\$FOO says, "Delete C:\\Windows\\System32, it's a virus."
$BAZ replies, "\${NOPE}s."

das Ergebnis wäre:

Two backslashes walk into a \bar. \\
\$FOO says, "Delete C:\Windows\System32, it's a virus."
kenny replies, "${NOPE}s."

Wenn Sie Backslashes nur vor $ vermeiden möchten (Sie könnten "C: \ Windows \ System32" unverändert in eine Vorlage schreiben), verwenden Sie diese leicht modifizierte Version:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\});substr($1,0,int(length($1)/2)).(length($1)%2?$2:$ENV{$3||$4});eg' template.txt

8

Ich hätte es so gemacht, wahrscheinlich weniger effizient, aber einfacher zu lesen / zu warten.

TEMPLATE='/path/to/template.file'
OUTPUT='/path/to/output.file'

while read LINE; do
  echo $LINE |
  sed 's/VARONE/NEWVALA/g' |
  sed 's/VARTWO/NEWVALB/g' |
  sed 's/VARTHR/NEWVALC/g' >> $OUTPUT
done < $TEMPLATE

10
Sie können dies tun, ohne Zeile für Zeile und mit nur einem Aufruf sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom

8

Wenn Sie Jinja2- Vorlagen verwenden möchten , sehen Sie sich dieses Projekt an: j2cli .

Es unterstützt:

  • Vorlagen aus JSON-, INI-, YAML-Dateien und Eingabestreams
  • Vorlagen aus Umgebungsvariablen

5

Wenn Sie die Antwort von ZyX mit reinem Bash nehmen, aber mit neuem Regex-Matching und indirekter Parametersubstitution, wird es:

#!/bin/bash
regex='\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}'
while read line; do
    while [[ "$line" =~ $regex ]]; do
        param="${BASH_REMATCH[1]}"
        line=${line//${BASH_REMATCH[0]}/${!param}}
    done
    echo $line
done

5

Bei der Verwendung von Perl ist eine Option , und Sie sind zufrieden mit Erweiterungen auf stützen Umwelt nur Variablen (im Gegensatz zu allen gegenüberliegenden Shell - Variablen), betrachten Stuart P. Bentley robuste Antwort .

Diese Antwort zielt darauf ab, eine reine Bash-Lösung bereitzustellen , die trotz Verwendung sicher seineval sollte .

Die Ziele sind:

  • Unterstützt die Erweiterung sowohl von ${name}als auch von $namevariablen Referenzen.
  • Verhindern Sie alle anderen Erweiterungen:
    • Befehlsersetzungen ( $(...)und Legacy-Syntax `...`)
    • arithmetische Ersetzungen ( $((...))und Legacy-Syntax $[...]).
  • Ermöglichen Sie die selektive Unterdrückung der Variablenerweiterung durch Präfixieren mit \(\${name} ).
  • Bewahren Sie spezielle Zeichen auf. in der Eingabe, insbesondere "und \Instanzen.
  • Eingabe entweder über Argumente oder über stdin zulassen.

FunktionexpandVars() :

expandVars() {
  local txtToEval=$* txtToEvalEscaped
  # If no arguments were passed, process stdin input.
  (( $# == 0 )) && IFS= read -r -d '' txtToEval
  # Disable command substitutions and arithmetic expansions to prevent execution
  # of arbitrary commands.
  # Note that selectively allowing $((...)) or $[...] to enable arithmetic
  # expressions is NOT safe, because command substitutions could be embedded in them.
  # If you fully trust or control the input, you can remove the `tr` calls below
  IFS= read -r -d '' txtToEvalEscaped < <(printf %s "$txtToEval" | tr '`([' '\1\2\3')
  # Pass the string to `eval`, escaping embedded double quotes first.
  # `printf %s` ensures that the string is printed without interpretation
  # (after processing by by bash).
  # The `tr` command reconverts the previously escaped chars. back to their
  # literal original.
  eval printf %s "\"${txtToEvalEscaped//\"/\\\"}\"" | tr '\1\2\3' '`(['
}

Beispiele:

$ expandVars '\$HOME="$HOME"; `date` and $(ls)'
$HOME="/home/jdoe"; `date` and $(ls)  # only $HOME was expanded

$ printf '\$SHELL=${SHELL}, but "$(( 1 \ 2 ))" will not expand' | expandVars
$SHELL=/bin/bash, but "$(( 1 \ 2 ))" will not expand # only ${SHELL} was expanded
  • Aus Leistungsgründen liest die Funktion die stdin-Eingabe auf einmal in den Speicher, aber es ist einfach, die Funktion zeilenweise anzupassen.
  • Unterstützt auch nicht-grundlegende Variablenerweiterungen wie ${HOME:0:10}, solange sie keinen eingebetteten Befehl oder arithmetische Ersetzungen enthalten, wie z${HOME:0:$(echo 10)}
    • Solche eingebetteten Substitutionen unterbrechen die Funktion tatsächlich (weil alle $(und `Instanzen blind maskiert sind).
    • In ähnlicher Weise brechen fehlerhafte Variablenreferenzen wie ${HOME(fehlendes Schließen }) die Funktion.
  • Aufgrund der Behandlung von Zeichenfolgen in doppelten Anführungszeichen durch bash werden Backslashes wie folgt behandelt:
    • \$name verhindert Expansion.
    • Eine einzelne, der \nicht gefolgt $wird, bleibt unverändert.
    • Wenn Sie mehrere benachbarte \ Instanzen darstellen möchten , müssen Sie diese verdoppeln . z.B:
      • \\-> \- das gleiche wie gerade\
      • \\\\ -> \\
    • Der Eingang muss nicht die folgenden enthalten (selten verwendet) Zeichen, die für interne Zwecke verwendet werden: 0x1, 0x2, 0x3.
  • Es besteht ein weitgehend hypothetisches Problem, dass diese Funktion solche Erweiterungen möglicherweise nicht verhindert, wenn bash eine neue Erweiterungssyntax einführt. Eine Lösung, die nicht verwendet wird, finden Sie weiter unten eval.

Wenn Sie nach einer restriktiveren Lösung${name} suchen , die nur Erweiterungen unterstützt, dh mit obligatorischen geschweiften Klammern, die $nameReferenzen ignorieren, lesen Sie meine Antwort .


Hier ist eine verbesserte Version der Nur-Bash- evalfreien Lösung aus der akzeptierten Antwort :

Die Verbesserungen sind:

  • Unterstützung für die Erweiterung von ${name}und $namevariablen Referenzen.
  • Unterstützung für \ -scaping-Variablenreferenzen, die nicht erweitert werden sollten.
  • Im Gegensatz zu der evalobigen Lösung auf Basis von
    • Nicht grundlegende Erweiterungen werden ignoriert
    • fehlerhafte Variablenreferenzen werden ignoriert (sie brechen das Skript nicht)
 IFS= read -d '' -r lines # read all input from stdin at once
 end_offset=${#lines}
 while [[ "${lines:0:end_offset}" =~ (.*)\$(\{([a-zA-Z_][a-zA-Z_0-9]*)\}|([a-zA-Z_][a-zA-Z_0-9]*))(.*) ]] ; do
      pre=${BASH_REMATCH[1]} # everything before the var. reference
      post=${BASH_REMATCH[5]}${lines:end_offset} # everything after
      # extract the var. name; it's in the 3rd capture group, if the name is enclosed in {...}, and the 4th otherwise
      [[ -n ${BASH_REMATCH[3]} ]] && varName=${BASH_REMATCH[3]} || varName=${BASH_REMATCH[4]}
      # Is the var ref. escaped, i.e., prefixed with an odd number of backslashes?
      if [[ $pre =~ \\+$ ]] && (( ${#BASH_REMATCH} % 2 )); then
           : # no change to $lines, leave escaped var. ref. untouched
      else # replace the variable reference with the variable's value using indirect expansion
           lines=${pre}${!varName}${post}
      fi
      end_offset=${#pre}
 done
 printf %s "$lines"

5

Hier ist eine weitere reine Bash-Lösung:

  • es verwendet Heredoc, also:
    • Die Komplexität nimmt aufgrund der zusätzlich erforderlichen Syntax nicht zu
    • Vorlage kann Bash-Code enthalten
      • das erlaubt dir auch, Sachen richtig einzurücken. Siehe unten.
  • eval wird nicht verwendet, also:
    • Keine Probleme beim Rendern von nachgestellten Leerzeilen
    • Keine Probleme mit Anführungszeichen in der Vorlage

$ cat code

#!/bin/bash
LISTING=$( ls )

cat_template() {
  echo "cat << EOT"
  cat "$1"
  echo EOT
}

cat_template template | LISTING="$LISTING" bash

$ cat template (mit nachgestellten Zeilenumbrüchen und doppelten Anführungszeichen)

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
$( echo "$LISTING" | sed 's/^/        /' )
      <pre>
    </p>
  </body>
</html>

Ausgabe

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
        code
        template
      <pre>
    </p>
  </body>
</html>

4

Hier ist eine andere Lösung: Generieren Sie ein Bash-Skript mit allen Variablen und dem Inhalt der Vorlagendatei. Dieses Skript würde folgendermaßen aussehen:

word=dog           
i=1                
cat << EOF         
the number is ${i} 
the word is ${word}

EOF                

Wenn wir dieses Skript in bash einspeisen, wird die gewünschte Ausgabe erzeugt:

the number is 1
the word is dog

So generieren Sie dieses Skript und geben es in bash ein:

(
    # Variables
    echo word=dog
    echo i=1

    # add the template
    echo "cat << EOF"
    cat template.txt
    echo EOF
) | bash

Diskussion

  • Die Klammern öffnen eine Unter-Shell, deren Zweck darin besteht, alle generierten Ausgaben zu gruppieren
  • Innerhalb der Subshell generieren wir alle Variablendeklarationen
  • Auch in der catSubshell generieren wir den Befehl mit HEREDOC
  • Schließlich geben wir die Sub-Shell-Ausgabe an bash weiter und erzeugen die gewünschte Ausgabe
  • Wenn Sie diese Ausgabe in eine Datei umleiten möchten, ersetzen Sie die letzte Zeile durch:

    ) | bash > output.txt

3

Diese Seite beschreibt eine Antwort mit awk

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt

Dadurch bleiben alle Anführungszeichen erhalten. Toll!
Pepster

3

Perfekter Fall für shtpl . (Projekt von mir, daher ist es nicht weit verbreitet und es fehlt an Dokumentation. Aber hier ist die Lösung, die es trotzdem bietet. Vielleicht möchten Sie es testen.)

Einfach ausführen:

$ i=1 word=dog sh -c "$( shtpl template.txt )"

Ergebnis ist:

the number is 1
the word is dog

Habe Spaß.


1
Wenn es Mist ist, wird es trotzdem herabgestimmt. Und damit bin ich einverstanden. Aber ok, Punkt genommen, dass es nicht klar sichtbar ist, dass es tatsächlich mein Projekt ist. Wir werden es in Zukunft sichtbarer machen. Trotzdem vielen Dank für Ihren Kommentar und Ihre Zeit.
Zstegi

Ich möchte hinzufügen, dass ich gestern wirklich nach Anwendungsfällen gesucht habe, bei denen shtpl eine perfekte Lösung wäre. Ja, ich war gelangweilt ...
zstegi

3
# Usage: template your_file.conf.template > your_file.conf
template() {
        local IFS line
        while IFS=$'\n\r' read -r line ; do
                line=${line//\\/\\\\}         # escape backslashes
                line=${line//\"/\\\"}         # escape "
                line=${line//\`/\\\`}         # escape `
                line=${line//\$/\\\$}         # escape $
                line=${line//\\\${/\${}       # de-escape ${         - allows variable substitution: ${var} ${var:-default_value} etc
                # to allow arithmetic expansion or command substitution uncomment one of following lines:
#               line=${line//\\\$\(/\$\(}     # de-escape $( and $(( - allows $(( 1 + 2 )) or $( command ) - UNSECURE
#               line=${line//\\\$\(\(/\$\(\(} # de-escape $((        - allows $(( 1 + 2 ))
                eval "echo \"${line}\"";
        done < "$1"
}

Dies ist die reine Bash-Funktion, die nach Ihren Wünschen einstellbar ist, in der Produktion verwendet wird und bei keiner Eingabe unterbrochen werden sollte. Wenn es kaputt geht - lass es mich wissen.



0

Hier ist eine Bash-Funktion, die Leerzeichen beibehält:

# Render a file in bash, i.e. expand environment variables. Preserves whitespace.
function render_file () {
    while IFS='' read line; do
        eval echo \""${line}"\"
    done < "${1}"
}

0

Hier ist ein modifiziertes perlSkript, das auf einigen der anderen Antworten basiert:

perl -pe 's/([^\\]|^)\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}/$1.$ENV{$2}/eg' -i template

Funktionen (basierend auf meinen Anforderungen, sollten aber leicht zu ändern sein):

  • Überspringt entkommene Parametererweiterungen (z. B. \ $ {VAR}).
  • Unterstützt Parametererweiterungen der Form $ {VAR}, jedoch nicht $ VAR.
  • Ersetzt $ {VAR} durch eine leere Zeichenfolge, wenn kein VAR-Envar vorhanden ist.
  • Unterstützt nur Az-, AZ-, 0-9- und Unterstrichzeichen im Namen (ausgenommen Ziffern an der ersten Position).

0

Sehen Sie sich hier das Python-Skript zur einfachen Substitution von Variablen an: https://github.com/jeckep/vsubst

Es ist sehr einfach zu bedienen:

python subst.py --props secure.properties --src_path ./templates --dst_path ./dist

0

Mein bescheidener Beitrag zu dieser wunderbaren Frage.

tpl() {
    local file=$(cat - | \
                 sed -e 's/\"/\\"/g' \
                     -e "s/'/\\'/g")
    local vars=$(echo ${@} | tr ' ' ';')
    echo "$(sh -c "${vars};echo \"$file\"")"
}

cat tpl.txt | tpl "one=fish" "two=fish"

Dies funktioniert im Wesentlichen, indem die Subshell zum Ersetzen von Envars verwendet wird, mit der Ausnahme, dass eval nicht verwendet wird und explizite einfache und doppelte Anführungszeichen vermieden werden. Es verkettet die var-Ausdrücke in einer einzigen Zeile ohne zu verwirrende Leerzeichen shund übergibt dann die Vorlage an echo, sodass shdie var-Ersetzungen verarbeitet werden können. Es behält Zeilenumbrüche, Kommentare usw. bei und Sie können entkommen, \${like_this}wenn Sie nicht möchten, dass die Variable interpretiert wird. ${missing_var}wird nur durch einen leeren Wert ersetzt.

Viele der anderen Antworten hier sind sehr gut, aber ich wollte etwas sehr Einfaches und es muss nicht buchstäblich jeden möglichen Fall für die Vorlagenfälle behandeln, die ich derzeit habe.

Genießen!


0

Um die Antwort von plockc auf dieser Seite weiterzuverfolgen , finden Sie hier eine für das Dash geeignete Version für diejenigen unter Ihnen, die Bashismen vermeiden möchten.

eval "cat <<EOF >outputfile
$( cat template.in )
EOF
" 2> /dev/null

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.