Bash Templating: Comment créer des fichiers de configuration à partir de modèles avec Bash?

134

J'écris un script pour automatiser la création de fichiers de configuration pour Apache et PHP pour mon propre serveur Web. Je ne veux pas utiliser d'interface graphique comme CPanel ou ISPConfig.

J'ai quelques modèles de fichiers de configuration Apache et PHP. Le script Bash doit lire les modèles, effectuer une substitution de variable et générer des modèles analysés dans un dossier. Quelle est la meilleure façon de le faire? Je peux penser à plusieurs façons. Laquelle est la meilleure ou peut-être existe-t-il de meilleures façons de le faire? Je veux faire ça en pure Bash (c'est facile en PHP par exemple)

1) Comment remplacer les espaces réservés $ {} dans un fichier texte?

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"

BTW, comment rediriger la sortie vers un fichier externe ici? Dois-je échapper à quelque chose si les variables contiennent, par exemple, des guillemets?

2) Utilisation de cat & sed pour remplacer chaque variable par sa valeur:

Template.txt donné:

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

Commander:

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

Cela me semble mauvais à cause de la nécessité d'échapper à de nombreux symboles différents et avec de nombreuses variables, la ligne sera trop longue.

Pouvez-vous penser à une autre solution élégante et sûre?

Vladislav Rastrusny
la source
Est-ce que cela répond à votre question? Comment remplacer les espaces réservés $ {} dans un fichier texte?
Ben Leggiero

Réponses:

61

Vous pouvez utiliser ceci:

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

pour remplacer toutes les ${...}chaînes par les variables d'environnement correspondantes (n'oubliez pas de les exporter avant d'exécuter ce script).

Pour le pur bash, cela devrait fonctionner (en supposant que les variables ne contiennent pas de chaînes $ {...}):

#!/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

. Solution qui ne se bloque pas si RHS fait référence à une variable qui se réfère à elle-même:

#!/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}"

AVERTISSEMENT : je ne connais pas un moyen de gérer correctement les entrées avec les NUL dans bash ou de préserver la quantité de retours à la ligne de fin. La dernière variante est présentée telle quelle car les shells «aiment» l'entrée binaire:

  1. read interprétera les contre-obliques.
  2. read -r n'interprétera pas les contre-obliques, mais supprimera quand même la dernière ligne si elle ne se termine pas par une nouvelle ligne.
  3. "$(…)"supprimera autant de nouvelles lignes de fin qu'il y en a, donc je termine par ; echo -n aet j'utilise echo -n "${line:0:-1}": cela supprime le dernier caractère (qui est a) et préserve autant de nouvelles lignes de fin qu'il y en avait dans l'entrée (y compris non).
ZyX
la source
3
Je changerais [^}]pour [A-Za-Z_][A-Za-z0-9_]dans la version bash pour empêcher le shell d'aller au-delà de la substitution stricte (par exemple s'il essayait de traiter ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen
2
@FractalizeR vous voudrez peut-être changer $&dans la solution Perl en "": d'abord laisse ${...}intact si elle ne parvient pas à se substituer, ensuite le remplace par une chaîne vide.
ZyX
5
REMARQUE: Apparemment, il y a eu un changement de bash 3.1 à 3.2 (et plus) dans lequel les guillemets simples autour de l'expression régulière - traitent le contenu de l'expression régulière comme une chaîne littérale. Ainsi, l'expression régulière ci-dessus devrait être ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Blue Waters
2
Pour que la whileboucle lise la dernière ligne même si elle n'est pas terminée par une nouvelle ligne, utilisez while read -r line || [[ -n $line ]]; do. En outre, votre readcommande supprime les espaces de début et de fin de chaque ligne; pour éviter cela, utilisezwhile IFS= read -r line || [[ -n $line ]]; do
mklement0
2
Juste pour noter une contrainte pour ceux qui recherchent une solution complète: ces solutions autrement pratiques ne vous permettent pas de protéger sélectivement les références de variables de l'expansion (par exemple en les \ échappant).
mklement0
138

Essayer envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
yottatsa
la source
12
Juste pour référence, ce envsubstn'est pas nécessaire lors de l'utilisation d'un heredoc car bash traite l'heredoc comme une chaîne littérale entre guillemets doubles et interpole déjà les variables qu'il contient. C'est un excellent choix lorsque vous souhaitez lire le modèle à partir d'un autre fichier. Un bon remplacement pour les beaucoup plus encombrants m4.
beporter
2
J'ai été très agréablement surpris d'apprendre l'existence de cette commande. J'essayais de bricoler manuellement les fonctionnalités d'envsubst sans succès. Merci yottatsa!
Tim Stewart
4
Remarque: envsubstest un utilitaire GNU gettext, et n'est en fait pas très robuste (puisque gettext est destiné à localiser les messages humains). Plus important encore, il ne reconnaît pas les substitutions $ {VAR} échappées par une barre oblique inverse (vous ne pouvez donc pas avoir un modèle qui utilise des substitutions $ VAR au moment de l'exécution, comme un script shell ou un fichier de configuration Nginx). Voir ma réponse pour une solution qui gère les échappements de barre oblique inverse.
Stuart P. Bentley
4
@beporter Dans ce cas, si vous vouliez passer ce modèle à envsubst pour une raison quelconque, vous voudriez l'utiliser <<"EOF", qui n'interpole pas les variables (les terminateurs entre guillemets sont comme les guillemets simples d'heredocs).
Stuart P. Bentley
2
Je l'ai utilisé comme: cat template.txt | envsubst
truthadjustr
47

envsubst était nouveau pour moi. Fantastique.

Pour mémoire, l'utilisation d'un heredoc est un excellent moyen de modéliser un fichier de configuration.

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
Dan Garthwaite
la source
1
Je préfère cela mieux que envsubstparce que cela m'a sauvé de l'addition apt-get install gettext-basedans mon Dockerfile
truthadjustr
Le shell en tant que script de type Template cependant sans aucune installation de bibliothèque externe ni stress de faire face aux expressions délicates.
千 木 郷
35

Je suis d'accord avec l'utilisation de sed: c'est le meilleur outil de recherche / remplacement. Voici mon approche:

$ 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
Hai Vu
la source
1
Cela nécessite un fichier temporaire pour la chaîne de substitution, non? Existe-t-il un moyen de faire cela sans fichiers temporaires?
Vladislav Rastrusny
@FractalizeR: Certaines versions de sed ont une -ioption (modifier les fichiers en place) qui est similaire à l' option perl . Consultez la page de manuel de votre sed .
Chris Johnsen
@FractalizeR Oui, sed -i remplacera inline. Si vous êtes à l'aise avec Tcl (un autre langage de script), consultez ce fil de discussion: stackoverflow.com/questions/2818130/...
Hai Vu
J'ai créé le replace.sed à partir d'un fichier de propriétés avec la commande sed suivante: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g 'the.properties> replace.sed
Jaap D
Le code de @hai vu crée un programme sed et passe ce programme en utilisant l'indicateur -f de sed. Si vous le souhaitez, vous pouvez à la place passer chaque ligne du programme sed dans sed en utilisant les indicateurs -e. FWIW J'aime l'idée d'utiliser sed pour la création de modèles.
Andrew Allbright
23

Je pense qu'eval fonctionne vraiment bien. Il gère les modèles avec des sauts de ligne, des espaces et toutes sortes de choses bash. Si vous avez un contrôle total sur les modèles eux-mêmes, bien sûr:

$ 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"

Cette méthode doit être utilisée avec précaution, bien sûr, car eval peut exécuter du code arbitraire. Exécuter ceci en tant que root est quasiment hors de question. Les citations du modèle doivent être échappées, sinon elles seront mangées pareval .

Vous pouvez également utiliser ici des documents si vous préférez catàecho

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

@plockc a proposé une solution qui évite le problème d'échappatoire des citations bash:

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

Éditer: Suppression de la partie sur l'exécution de ceci en tant que root en utilisant sudo ...

Edit: Ajout d'un commentaire sur la façon dont les citations doivent être échappées, ajout de la solution de plockc au mix!

mogsie
la source
Cela supprime les citations que vous avez dans votre modèle et ne se substitue pas à l'intérieur de guillemets simples, donc en fonction du format de votre modèle, peut conduire à des bogues subtils. Ceci est probablement applicable à toute méthode de création de modèles basée sur Bash, cependant.
Alex B
Les modèles basés sur IMHO Bash sont de la folie, car vous devez être un programmeur bash pour comprendre ce que fait votre modèle! Mais merci pour le commentaire!
mogsie
@AlexB: Cette approche se substituer entre guillemets simples, comme ils sont juste des caractères littéraux dans la chaîne entre guillemets entourant plutôt que délimiteurs de chaîne lorsque l' evalédition echo / catcommande des processus eux; essayez eval "echo \"'\$HOME'\"".
mklement0
21

J'ai une solution bash comme mogsie mais avec heredoc au lieu de herestring pour vous permettre d'éviter d'échapper des guillemets doubles

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
plockc
la source
4
Cette solution prend en charge l' expansion des paramètres Bash dans le modèle. Mes favoris sont des paramètres obligatoires avec ${param:?}un texte d'imbrication autour de paramètres optionnels. Exemple: se ${DELAY:+<delay>$DELAY</delay>}développe en rien lorsque DELAY n'est pas défini et <delay> 17 </delay> lorsque DELAY = 17.
Eric Bolinger
2
Oh! Et le délimiteur EOF peut utiliser une chaîne dynamique, comme le PID _EOF_$$.
Eric Bolinger
1
@ mklement0 Une solution de contournement pour les retours à la ligne de fin est d'utiliser une certaine expansion comme par exemple une variable vide $trailing_newline, ou d'utiliser $NL5et de s'assurer qu'elle est développée en 5 nouvelles lignes.
xebeche
@xebeche: Oui, placer ce que vous suggérez à la toute fin à l' intérieurtemplate.txt fonctionnerait afin de préserver les nouvelles lignes de fin.
mklement0
1
Une solution élégante, mais notez que la substitution de commande supprimera toutes les nouvelles lignes de fin du fichier d'entrée, bien que ce ne soit généralement pas un problème. Un autre cas particulier: en raison de l'utilisation de eval, if template.txtcontains EOFsur une ligne propre, il terminera prématurément l'ici-doc et interrompra ainsi la commande. (Pointe du chapeau à @xebeche).
mklement0
18

Edit 6 janvier 2017

J'avais besoin de garder des guillemets doubles dans mon fichier de configuration, donc les doubles guillemets d'échappement avec sed aident:

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

Je ne peux pas penser à garder de nouvelles lignes à la fin, mais les lignes vides entre les deux sont conservées.


Bien que ce soit un vieux sujet, l'OMI j'ai trouvé une solution plus élégante ici: 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

Tous les crédits à Grégory Pakosz .

CKK
la source
Cela supprime les guillemets doubles de l'entrée et, s'il y a plusieurs retours à la ligne de fin dans le fichier d'entrée, les remplace par un seul.
mklement0
2
J'avais besoin de deux contre-obliques de moins pour que cela fonctionne, c'est-à-dire eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau
Malheureusement, cette approche ne vous permet pas de modéliser les fichiers php (qu'ils contiennent $variables).
IStranger le
10

Au lieu de réinventer la roue, allez avec envsubst Peut être utilisé dans presque tous les scénarios, par exemple la création de fichiers de configuration à partir de variables d'environnement dans des conteneurs de docker.

Si sur mac, assurez-vous d'avoir un homebrew, puis liez-le à partir de 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

Maintenant, utilisez-le simplement:

# 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
smentek
la source
cette séquence d'invocation envsubstfonctionne réellement.
Alex
Pour quelqu'un d' autre avenir, envsubstne fonctionne pas sur Mac OS, vous auriez besoin de l' installer à l' aide homebrew: brew install gettext.
Matt
9

Une version plus longue mais plus robuste de la réponse acceptée:

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

Cela étend toutes les instances de $VAR ou ${VAR} à leurs valeurs d'environnement (ou, si elles ne sont pas définies, la chaîne vide).

Il échappe correctement les contre-obliques et accepte un $ échappé par une barre oblique inverse pour empêcher la substitution (contrairement à envsubst, qui, en fait, ne fait pas cela ).

Donc, si votre environnement est:

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

et votre modèle est:

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

le résultat serait:

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

Si vous souhaitez uniquement échapper les contre-obliques avant $ (vous pouvez écrire "C: \ Windows \ System32" dans un modèle inchangé), utilisez cette version légèrement modifiée:

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
Stuart P. Bentley
la source
8

Je l'aurais fait de cette façon, probablement moins efficace, mais plus facile à lire / maintenir.

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
Craig552uk
la source
10
Vous pouvez le faire sans lire ligne par ligne et avec une seule invocation sed:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom
8

Si vous souhaitez utiliser des modèles Jinja2 , consultez ce projet: j2cli .

Elle supporte:

  • Modèles de fichiers JSON, INI, YAML et de flux d'entrée
  • Création de modèles à partir de variables d'environnement
kolypto
la source
5

En prenant la réponse de ZyX en utilisant pure bash mais avec une nouvelle correspondance de regex de style et une substitution indirecte de paramètres, cela devient:

#!/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
qui
la source
5

Si l'utilisation de Perl est une option et que vous vous contentez de baser les extensions sur des variables d' environnement uniquement (par opposition à toutes les variables shell ), considérez la réponse robuste de Stuart P. Bentley .

Cette réponse vise à fournir une solution uniquement bash qui, malgré son utilisation eval, devrait être sûre à utiliser .

Les objectifs sont:

  • Prend en charge l'expansion des références ${name}et $namedes références variables.
  • Empêchez toutes les autres extensions:
    • substitutions de commandes ( $(...)et syntaxe héritée `...`)
    • substitutions arithmétiques ( $((...))et syntaxe héritée $[...]).
  • Autorise la suppression sélective de l'expansion des variables en préfixant \( \${name}).
  • Conservez les caractères spéciaux. dans l'entrée, notamment "et les \instances.
  • Autoriser l'entrée via des arguments ou via stdin.

FonctionexpandVars() :

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' '`(['
}

Exemples:

$ 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
  • Pour des raisons de performances, la fonction lit l'entrée stdin en même temps en mémoire, mais il est facile d'adapter la fonction à une approche ligne par ligne.
  • Prend également en charge les extensions de variables non basiques telles que ${HOME:0:10}, tant qu'elles ne contiennent pas de commande intégrée ou de substitutions arithmétiques, telles que${HOME:0:$(echo 10)}
    • Ces substitutions intégrées BREAK la fonction (car toutes les instances $(et `sont aveuglément échappées).
    • De même, des références de variable mal formées telles que ${HOME(fermeture manquante }) BREAK la fonction.
  • En raison de la gestion par bash des chaînes entre guillemets, les barres obliques inverses sont traitées comme suit:
    • \$name empêche l'expansion.
    • Un single \non suivi de $est conservé tel quel.
    • Si vous souhaitez représenter plusieurs \ instances adjacentes , vous devez les doubler ; par exemple:
      • \\-> \- le même que juste\
      • \\\\ -> \\
    • L'entrée ne doit pas contenir les caractères suivants (rarement utilisés), qui sont utilisés à des fins internes: 0x1, 0x2, 0x3.
  • Il y a une préoccupation en grande partie hypothétique que si bash devrait introduire une nouvelle syntaxe d'expansion, cette fonction pourrait ne pas empêcher de telles expansions - voir ci-dessous pour une solution qui n'utilise pas eval.

Si vous recherchez une solution plus restrictive qui ne prend en charge que les ${name}extensions - c'est-à-dire, avec des accolades obligatoires , en ignorant les $nameréférences - voyez cette réponse .


Voici une version améliorée de la evalsolution bash-only, -free à partir de la réponse acceptée :

Les améliorations sont:

  • Prise en charge de l'expansion des références ${name}et $namedes références variables.
  • Prise en \charge des références de variables -escaping qui ne devraient pas être développées.
  • Contrairement à la evalsolution basée ci-dessus,
    • les extensions non basiques sont ignorées
    • les références de variables mal formées sont ignorées (elles ne cassent pas le script)
 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"
mklement0
la source
5

Voici une autre solution pure bash:

  • il utilise heredoc, donc:
    • la complexité n'augmente pas en raison de la syntaxe supplémentaire requise
    • le modèle peut inclure du code bash
      • cela vous permet également d'indenter correctement les éléments. Voir ci-dessous.
  • il n'utilise pas eval, donc:
    • aucun problème avec le rendu des lignes vides de fin
    • aucun problème avec les citations dans le modèle

$ cat code

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

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

cat_template template | LISTING="$LISTING" bash

$ cat template (avec retours à la ligne et guillemets doubles)

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

production

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
        code
        template
      <pre>
    </p>
  </body>
</html>
Tomáš Pospíšek
la source
4

Voici une autre solution: générer un script bash avec toutes les variables et le contenu du fichier modèle, ce script ressemblerait à ceci:

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

EOF                

Si nous alimentons ce script dans bash, il produirait la sortie souhaitée:

the number is 1
the word is dog

Voici comment générer ce script et alimenter ce script dans bash:

(
    # Variables
    echo word=dog
    echo i=1

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

Discussion

  • Les parenthèses ouvrent un sous shell, son but est de regrouper l'ensemble de la sortie générée
  • Dans le sous-shell, nous générons toutes les déclarations de variables
  • Toujours dans le sous-shell, nous générons la catcommande avec HEREDOC
  • Enfin, nous alimentons la sortie du sous-shell vers bash et produisons la sortie souhaitée
  • Si vous souhaitez rediriger cette sortie dans un fichier, remplacez la dernière ligne par:

    ) | bash > output.txt
Hai Vu
la source
3

Cette page décrit une réponse avec awk

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt
Marron mat
la source
Cela garde toutes les citations intactes. Génial!
Pepster
3

Cas parfait pour shtpl . (projet de la mienne, donc il n'est pas largement utilisé et manque de documentation. Mais voici la solution qu'il offre quand même. Pouvez-vous la tester.)

Exécutez simplement:

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

Le résultat est:

the number is 1
the word is dog

S'amuser.

zstegi
la source
1
Si c'est de la merde, c'est quand même défavorisé. Et je suis d'accord avec ça. Mais ok, remarquez, que ce n'est pas clairement visible, que c'est en fait mon projet. Va le rendre plus visible à l'avenir. Merci en tout cas pour ton commentaire et ton temps.
zstegi
Je veux ajouter que j'ai vraiment recherché des cas d'utilisation hier, où shtpl serait une solution parfaite. Ouais, je m'ennuyais ...
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"
}

Il s'agit de la fonction pure bash ajustable à votre goût, utilisée en production et qui ne doit s'arrêter sur aucune entrée. Si ça casse - faites le moi savoir.

ttt
la source
0

Voici une fonction bash qui préserve les espaces:

# 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}"
}
Igor Katson
la source
0

Voici un perlscript modifié basé sur quelques-unes des autres réponses:

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

Fonctionnalités (basées sur mes besoins, mais devraient être faciles à modifier):

  • Ignore les extensions de paramètres échappés (par exemple \ $ {VAR}).
  • Prend en charge les extensions de paramètres de la forme $ {VAR}, mais pas $ VAR.
  • Remplace $ {VAR} par une chaîne vide s'il n'y a pas d'envar VAR.
  • Ne prend en charge que les caractères az, AZ, 0-9 et de soulignement dans le nom (à l'exclusion des chiffres en première position).
Kevin
la source
0

Regardez le script python de substitution de variables simples ici: https://github.com/jeckep/vsubst

C'est très simple à utiliser:

python subst.py --props secure.properties --src_path ./templates --dst_path ./dist
jeckep
la source
0

Pour suivre la réponse de plockc sur cette page, voici une version adaptée au tableau de bord, pour ceux d'entre vous qui cherchent à éviter les bashismes.

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