Templating avec Linux dans un script shell?

26

ce que je veux accomplir c'est:

1.) Avoir un fichier de configuration comme modèle, avec des variables comme $ version $ path (par exemple apache config)

2.) Avoir un script shell qui "remplit" les variables du modèle et écrit le fichier généré sur le disque.

Est-ce possible avec un script shell. Je serais très reconnaissant si vous pouvez nommer quelques commandes / outils que je peux accomplir ceci ou quelques bons liens.

Markus
la source

Réponses:

23

C'est très possible. Un moyen très simple de l'implémenter serait que le fichier modèle soit en fait le script et utilise des variables shell telles que

#! /bin/bash
version="1.2.3"
path="/foo/bar/baz"
cat > /tmp/destfile <<-EOF
here is some config for version $version which should
also reference this path $path
EOF

Vous pouvez même rendre cela configurable sur la ligne de commande en spécifiant version=$1et path=$2, vous pouvez donc l'exécuter comme bash script /foo/bar/baz 1.2.3. L' -avant EOF provoque des espaces avant que les lignes soient ignorées, utilisez plain <<EOFsi vous ne voulez pas ce comportement.

Une autre façon de procéder serait d'utiliser la fonctionnalité de recherche et de remplacement de sed

#! /bin/bash
version="1.2.3"
path="/foo/bar/baz"
sed -e "s/VERSION/$version/g" -e "s/PATH/$path/" /path/to/templatefile > /tmp/destfile

qui remplacerait chaque instance des chaînes VERSION et PATH. S'il y a d'autres raisons pour lesquelles ces chaînes se trouveraient dans le fichier modèle, vous pouvez effectuer votre recherche et remplacer la version VERSION ou% VERSION% ou quelque chose de moins susceptible d'être déclenché accidentellement.

mtinberg
la source
18

Aucun outil nécessaire autre que /bin/sh. Étant donné un fichier modèle du formulaire

Version: ${version}
Path: ${path}

ou même avec un code shell mixte inclus

Version: ${version}
Path: ${path}
Cost: ${cost}\$
$(i=1; for w in one two three four; do echo Param${i}: ${w}; i=$(expr $i + 1); done)

et un fichier de configuration shell analysable comme

version="1.2.3-r42"
path="/some/place/under/the/rainbow/where/files/dance/in/happiness"
cost="42"

il est simple de l'étendre à

Version: 1.2.3-r42
Path: /some/place/under/the/rainbow/where/files/dance/in/happiness
Cost: 42$
Param1: one
Param2: two
Param3: three
Param4: four

En effet, étant donné le chemin d'accès au fichier de configuration dans la variable shell config_fileet le chemin d'accès au fichier modèle dans template_file, il vous suffit de:

. ${config_file}
template="$(cat ${template_file})"
eval "echo \"${template}\""

C'est peut-être plus joli que d'avoir un script shell complet comme fichier modèle (solution de @ mtinberg).

Le programme d'extension de modèles naïf complet:

#!/bin/sh

PROG=$(basename $0)

usage()
{
    echo "${PROG} <template-file> [ <config-file> ]"
}

expand()
{
    local template="$(cat $1)"
    eval "echo \"${template}\""
}

case $# in
    1) expand "$1";;
    2) . "$2"; expand "$1";;
    *) usage; exit 0;;
esac

Cela produira l'extension à la sortie standard; redirigez simplement la sortie standard vers un fichier ou modifiez ce qui précède de manière évidente pour produire le fichier de sortie souhaité.

Avertissements: l' expansion du fichier modèle ne fonctionnerait pas si le fichier contenait des guillemets doubles ( "). Pour des raisons de sécurité, nous devrions probablement inclure des vérifications de cohérence évidentes ou, mieux encore, effectuer une transformation d'échappement du shell si le fichier modèle est généré par une entité externe.

FooF
la source
1
C'est formidable de voir encore des gens utiliser activement des scripts shell pour faire des trucs amusants comme ça. Un de mes amis a écrit une horloge en script shell. github.com/tablespoon/fun/blob/master/cli-clock Des moments amusants en effet.
poussins
2
Lorsque j'ai trouvé cette réponse quand on cherche à faire moi - même, je mis en œuvre l'approche assez naïve et affiché sur Github: github.com/nealian/bash-unsafe-templates
Pyrocater
9

La façon la plus simple de le faire simplement dans Linux CLI est d'utiliser envsubstles variables d'environnement.

Exemple de fichier modèle apache.tmpl:

<VirtualHost *:${PORT}>
    ServerName ${SERVER_NAME}
    ServerAlias ${SERVER_ALIAS}
    DocumentRoot "${DOCUMENT_ROOT}"
</VirtualHost>

Exécuter envsubstet exporter le résultat vers un nouveau fichier my_apache_site.conf:

export PORT="443"
export SERVER_NAME="example.com"
export SERVER_ALIAS="www.example.com"
export DOCUMENT_ROOT="/var/www/html/"
envsubst < apache.tmpl > my_apache_site.conf

Sortie:

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot "/var/www/html/"
</VirtualHost>
Lirt
la source
2
Il fait partie de GNU gettext, et est donc disponible sur toutes les principales plates-formes Unix (y compris macOS X), ainsi que Microsoft Windows.
tricasse
4

Vous devriez probablement envisager un système de gestion de configuration comme Puppet ou Chef . Ceux-ci peuvent facilement faire ce que vous décrivez ci-dessus et bien plus encore.

EEAA
la source
1
Merci. Absolument, j'ai installé et exécuté Chef. Mais cela ajoute beaucoup de frais généraux, lorsque vous devez écrire vos propres livres de cuisine. Je ne connais pas le langage de programmation ruby ​​et ma conclusion était. il est plus facile de le faire avec un script shell pour les cas les plus faciles (si possible).
Markus
On dirait que Puppet et Chef utilisent tous deux ERB pour les modèles, et c'est ridiculement facile à démarrer. Étant donné une variable name, la chaîne <%= name %>dans un modèle sera remplacée par namela valeur de. La façon dont vous définissez en namedehors du modèle diffère évidemment entre les deux systèmes.
Mike Renfro
1
Oui, les modèles (avec le chef) en eux-mêmes sont absolument faciles. Mais utiliser le chef comme cadre (et écrire les livres de cuisine) demande beaucoup de temps. Pour obtenir les données dans les modèles, vous devez comprendre où et comment Chef gère la «fusion» des sources de données et beaucoup d'autres choses. J'ai commencé à écrire mes propres livres de cuisine, mais un script shell serait dans mon cas spécial 100 fois plus rapide ...
Markus
Faire fonctionner l'infrastructure pour Chef ou Puppet peut être pénible ou vous pouvez essayer de comprendre comment les exécuter sans tête, ce qui est une aventure amusante. Ansible fonctionne très bien en mode pull ou push hors de la boîte afin qu'il puisse trouver un meilleur équilibre entre le comprendre et le scripter vous-même. docs.ansible.com/template_module.html
poussins
4

Si vous voulez des modèles légers et réels plutôt que du code shell qui génère de nouveaux fichiers, les choix habituels sont sed& awkou perl. Voici un lien: http://savvyadmin.com/generate-text-from-templates-scripts-and-csv-data/

Moi, j'utiliserais un vrai langage comme perl, tcl, python, ruby ​​ou autre chose dans cette classe. Quelque chose construit pour les scripts. Ils ont tous de bons outils de modélisation simples et des tonnes d'exemples dans Google.

marque
la source
4

J'utilise shtpl pour ça. (mon projet privé, ce qui signifie qu'il n'est pas largement utilisé. Mais peut-être voulez-vous le tester quand même)

Par exemple, vous voulez générer un / etc / network / interfaces à partir d'un fichier csv, vous pouvez le faire comme ça:

Contenu du fichier CSV (ici test.csv):

eth0;10.1.0.10;255.255.0.0;10.1.0.1
eth1;192.168.0.10; 255.255.255.0;192.168.0.1

Modèle (ici interfaces.tpl):

#% IFS=';'
#% while read "Val1" "Val2" "Val3" "Val4"; do
auto $Val1 
iface $Val1 inet static
  address $Val2 
  netmask $Val3 
  gateway $Val4 

#% done < "$CSVFILE"

Commander:

$ CSVFILE=test.csv sh -c "$( shtpl interfaces.tpl )"

Résultat:

auto eth0 
iface eth0 inet static
  address 10.1.0.10 
  netmask 255.255.0.0 
  gateway 10.1.0.1 

auto eth1 
iface eth1 inet static
  address 192.168.0.10 
  netmask  255.255.255.0 
  gateway 192.168.0.1

Prendre plaisir!

zstegi
la source
1

J'ai amélioré la réponse de FooF pour que l'utilisateur n'ait pas besoin d'effacer manuellement les guillemets doubles:

#!/bin/bash
template="$(cat $1)"
template=$(sed 's/\([^\\]\)"/\1\\"/g; s/^"/\\"/g' <<< "$template")
eval "echo \"${template}\""
Shaohua Li
la source
1

J'ai récemment publié un script bash qui accomplit exactement cela en utilisant une syntaxe de modèle de type jinja. Cela s'appelle un cookie . Voici une démo:

démo de cookie

Bryan Bugyi
la source
Comment cela répond-il à la question?
RalfFriedl
1
@RalfFriedl Je ne sais pas ce que tu veux dire. Je viens de relire la question en pensant que j'ai dû manquer quelque chose, mais je ne le vois pas. En fait, comment cela ne répond-il à aucune partie de la question qui a été posée? C'est presque comme si j'avais construit un cookie dans le seul but de répondre à cette question ... ce que j'ai fait, bien que j'aie pu le formuler un peu différemment dans ma tête à l'époque. :)
Bryan Bugyi
Je suis un peu en retard pour jouer le rôle de médiateur, mais peut-être Ralf vous a-t-il simplement charité d' expliquer comment le cookie répond à la question, plutôt que d'essayer d'impliquer de manière non charitable que vous ne connaissez pas suffisamment votre propre projet pour juger s'il pourrait résoudre le problème de quelqu'un .
abathur
0

Je suis probablement en retard à cette fête. Cependant, je suis tombé sur le même problème et j'ai opté pour la création de mon propre moteur de modèle BASH en quelques lignes de code:

Disons que vous avez ceci file.template:

# My template
## Author
 - @NAME@ <@EMAIL@>

Et ce rulesfichier:

NAME=LEOPOLDO WINSTON
EMAIL=leothewinston\@leoserver.com

Vous exécutez cette commande:

templater rules < file.template

Vous obtenez ceci:

# My template
## Author
 - LEOPOLDO WINSTON <[email protected]>

Vous pouvez l'installer en:

 bpkg install vicentebolea/bash-templater

Ceci est le site du projet

Vicente Bolea
la source
0

Pour développer la grande réponse de @ FooF (les nouvelles lignes n'ont pas été formatées dans les commentaires), en utilisant un caractère de contrôle hérédoc +, vous pouvez autoriser des caractères et des noms de fichiers arbitraires :

template() {
    # if [ "$#" -eq 0 ] ; then return; fi # or just error
    eval "cat <<$(printf '\x04\x04\x04');
$(cat $1)
"
}

Cela accepte tout caractère non nul et ne tronque tôt que si 3 ^Doctets sont rencontrés sur leur propre ligne (en réalité jamais). zsh prend même en charge les terminateurs nuls, donc printf '\x00\x00\x00'cela fonctionnerait. templatefonctionne même pour les noms de fichiers perfides comme:

for num in `seq 10`; do
    template 'foo "$ .html' # works
done

Méfiez-vous des modèles de shell peuvent "étendre" des commandes arbitraires, par exemple $(launch_nukes.sh --target $(curl -sL https://freegeoip.app/csv/ | cut -d, -f 9,10)). Avec une grande puissance…

Edit: si vous ne voulez vraiment pas que vos fichiers lancent des armes nucléaires, juste sudo -u nobody sh(ou un autre utilisateur sûr) au préalable.

alexchandel
la source