Templando com Linux em um Shell Script?

26

o que eu quero realizar é:

1.) Ter um arquivo de configuração como modelo, com variáveis ​​como $ version $ path (por exemplo, apache config)

2.) Ter um script de shell que "preencha" as variáveis ​​do modelo e grave o arquivo gerado no disco.

Isso é possível com um script de shell. Eu ficaria muito grato se você puder citar alguns comandos / ferramentas que eu possa realizar isso ou alguns bons links.

Markus
fonte

Respostas:

23

Isto é muito possível. Uma maneira muito simples de implementar isso seria o arquivo de modelo ser o script e usar variáveis ​​de shell como

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

Você pode até tornar isso configurável na linha de comando especificando version=$1e path=$2, para que você possa executá-lo como bash script /foo/bar/baz 1.2.3. O -EOF anterior causa espaço em branco antes que as linhas sejam ignoradas, use plain <<EOFse você não quiser esse comportamento.

Outra maneira de fazer isso seria usar a pesquisa e substituir a funcionalidade do 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

que substituiria cada instância das seqüências VERSION e PATH. Se houver outros motivos para essas seqüências estarem no arquivo de modelo, você poderá fazer sua pesquisa e substituir ser VERSION ou% VERSION% ou algo com menos probabilidade de ser acionado acidentalmente.

mtinberg
fonte
18

Nenhuma ferramenta necessária além de /bin/sh. Dado um arquivo de modelo do formulário

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

ou mesmo com código shell misto incluído

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)

e um arquivo de configuração analisável pelo shell como

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

é uma questão simples expandir isso para

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

De fato, dado o caminho para o arquivo de configuração na variável shell config_filee o caminho para o arquivo de modelo template_file, tudo o que você precisa fazer é:

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

Talvez seja mais bonito do que ter um shell script completo como arquivo de modelo (a solução da @ mtinberg).

O programa completo de expansão de modelos ingênuos:

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

Isso produzirá a expansão para a saída padrão; basta redirecionar a saída padrão para um arquivo ou modificar as opções acima de maneira óbvia para produzir o arquivo de saída desejado.

Advertências: a expansão do arquivo de modelo não funcionaria se o arquivo contivesse aspas duplas sem escape ( "). Por motivos de segurança, provavelmente devemos incluir algumas verificações óbvias de integridade ou, melhor ainda, executar uma transformação de escape de shell se o arquivo de modelo for gerado por entidade externa.

FooF
fonte
11
É ótimo ainda ver as pessoas ativamente usando scripts de shell para fazer coisas divertidas como essa. Um amigo meu escreveu um relógio em shell script. github.com/tablespoon/fun/blob/master/cli-clock Tem tempos divertidos.
chicks
2
Quando eu encontrei essa resposta ao tentar fazer isso sozinho, implementei a abordagem bastante ingênua e a publiquei
Pyrocater
9

A maneira mais fácil de fazer isso simplesmente na CLI do Linux é usar envsubste variáveis ​​de ambiente.

Arquivo de modelo de exemplo apache.tmpl:

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

Execute envsubste envie o resultado para o novo arquivo 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

Saída:

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot "/var/www/html/"
</VirtualHost>
Lirt
fonte
2
Faz parte do GNU gettext e, portanto, está disponível nas principais plataformas Unix (incluindo o macOS X), bem como no Microsoft Windows.
tricasse em 30/03
4

Você provavelmente deveria procurar um sistema de gerenciamento de configuração como Puppet ou Chef . Eles podem facilmente fazer o que você descreve acima e muito mais.

EEAA
fonte
11
obrigado. Absolutamente, tenho o Chef instalado e funcionando. Mas isso acrescenta muita sobrecarga quando você precisa escrever seus próprios livros de receitas. Eu não sei a linguagem de programação ruby ​​e minha conclusão foi. é mais fácil fazer isso com um script de shell para os casos mais fáceis (se possível).
Markus
Parece que Puppet e Chef usam ERB para modelos, e isso é ridiculamente fácil de começar. Dada uma variável name, a cadeia de caracteres <%= name %>em um modelo será substituída namepelo valor de. Como você define namefora do modelo difere entre os dois sistemas, obviamente.
Mike Renfro
11
Sim, o modelo (com o Chef) em si é absolutamente fácil. Mas usar o chef como Framework (e escrever os livros de receitas) requer muito tempo. Para inserir os dados nos modelos, você precisa entender onde e como o Chef gerencia a "mesclagem" de fontes de dados e muitas outras coisas. Eu comecei a escrever meus próprios livros de receitas, mas um shell script que no meu caso especial será 100 vezes mais rápido ...
Markus
Obter a infraestrutura para Chef ou Puppet pode ser uma dor ou você pode tentar descobrir como executá-los sem cabeça, o que é uma aventura divertida. O Ansible funciona bem no modo de puxar ou empurrar para fora da caixa, para que possa ter um equilíbrio melhor entre descobrir e criar scripts por si mesmo. docs.ansible.com/template_module.html
chicks
4

Se você deseja modelos leves e reais, em vez de código shell que gera novos arquivos, as opções comuns são sed& awkou perl. Aqui está um link: http://savvyadmin.com/generate-text-from-templates-scripts-and-csv-data/

Eu usaria uma linguagem real como perl, tcl, python, ruby ​​ou qualquer outra coisa nessa classe. Algo criado para scripts. Todos eles têm boas ferramentas simples de modelagem e vários exemplos no google.

Marca
fonte
4

Eu uso o shtpl para isso. (projeto particular meu, o que significa que ele não é amplamente utilizado. Mas talvez você queira testá-lo de qualquer maneira)

Por exemplo, você deseja gerar um / etc / network / interfaces a partir de um arquivo csv, pode fazer o seguinte:

Conteúdo do arquivo CSV (aqui 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

Modelo (aqui interfaces.tpl):

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

#% done < "$CSVFILE"

Comando:

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

Resultado:

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

Apreciar!

zstegi
fonte
1

Aprimorei a resposta do FooF para que o usuário não precise descolar aspas duplas manualmente:

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

Recentemente, publiquei um script bash que realiza exatamente isso usando uma sintaxe de modelo semelhante a um jinja. Chama-se cookie . Aqui está uma demonstração:

demonstração de cookies

Bryan Bugyi
fonte
Como isso responde à pergunta?
RalfFriedl
11
@RalfFriedl Não sei ao certo o que você quer dizer. Acabei de reler a pergunta pensando que devo ter perdido algo, mas não estou vendo. De fato, como isso não responde a nenhuma parte da pergunta que foi feita? É quase como se eu tivesse construído um biscoito com o único objetivo de responder a essa pergunta ... o que eu fiz, embora eu possa ter escrito um pouco diferente na minha cabeça na época. :)
Bryan Bugyi
Estou um pouco atrasado para ser mediador, mas talvez Ralf estivesse apenas pedindo caridosamente que você explicasse como o cookie responde à pergunta, em vez de tentar implicar sem caridade que você não conhece seu próprio projeto o suficiente para julgar se isso pode resolver o problema de alguém. .
abathur 25/04
0

Provavelmente estou atrasado para esta festa. No entanto, deparei-me com o mesmo problema e optei por criar meu próprio mecanismo de modelo BASH em algumas linhas de código:

Vamos dizer que você tem isso file.template:

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

E esse rulesarquivo:

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

Você executa este comando:

templater rules < file.template

Você obtém isso:

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

Você pode instalá-lo:

 bpkg install vicentebolea/bash-templater

Este é o site do projeto

Vicente Bolea
fonte
0

Para expandir a ótima resposta do @ FooF (as novas linhas não foram formatadas nos comentários), usando caracteres heredoc + controle, você pode permitir caracteres e nomes de arquivos arbitrários :

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

Isso aceita qualquer caractere não nulo e somente trunca antecipadamente se ^Dforem encontrados 3 bytes em sua própria linha (realisticamente nunca). O zsh suporta até terminadores nulos, então printf '\x00\x00\x00'funcionaria. templateaté funciona para nomes de arquivos traiçoeiros como:

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

Cuidado com os modelos de shell podem "expandir" comandos arbitrários, por exemplo $(launch_nukes.sh --target $(curl -sL https://freegeoip.app/csv/ | cut -d, -f 9,10)). Com grande poder ...

Editar: se você realmente não deseja que seus arquivos iniciem armas nucleares para você, apenas sudo -u nobody sh(ou algum outro usuário seguro) de antemão.

alexchandel
fonte