Aqui está uma solução perl
usando Text::Template
. O script lê o modelo a partir de uma variável de string ( $tstr
), executa todas as substituições (incluindo algum código perl embutido para executar um loop sobre a matriz @ADMIN_IPS
e depois imprime o resultado:
(em um sistema debian, isso requer que o pacote libtext-template-perl
seja instalado)
#! /usr/bin/perl
use strict;
use Text::Template;
# The template, in a string ($tstr):
my $tstr='-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 80 -d {$PUBLIC_IP} --syn -j ACCEPT
{
foreach $a (@ADMIN_IPS) {
$OUT .= "-A INPUT -i $PUBLIC_INTERFACE -p tcp -m tcp --dport 22 -d $PUBLIC_IP -s $a --syn -j ACCEPT\n";
}
}
-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 443 -d {$PUBLIC_IP} --syn -j ACCEPT
';
# create the Text::Template object ($tt)
my $tt = Text::Template->new(TYPE => 'STRING', SOURCE => $tstr);
# define a hash reference to hold all the replacements:
my $vars = { PUBLIC_INTERFACE => 'eth0',
PUBLIC_IP => '8.8.8.8',
ADMIN_IPS => [ '8.8.10.1', '8.8.10.2', '8.8.10.3' ],
};
# fill in the template
my $text = $tt->fill_in(HASH => $vars);
print $text;
Saída:
-A INPUT -i eth0 -p tcp -m tcp --dport 80 -d 8.8.8.8 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.1 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.2 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.3 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 443 -d 8.8.8.8 --syn -j ACCEPT
Para a maioria dos modelos de luz como este, as variáveis escalar (valor único) e a lista ocasional (também conhecida como matriz) ou hash (matriz associativa) são tudo o que você precisa.
O script acima pode ser adaptado indefinidamente para todos os tipos de trabalhos semelhantes - basta alterar o modelo e a referência hash $vars
. E não é de todo difícil, por exemplo, carregar o template de um arquivo e as variáveis (escalares e arrays) de outro, então você pode ter um pequeno script reutilizável que aceita dois argumentos de arquivo (template e vars).
Além do próprio modelo e da configuração $vars
, há apenas cerca de cinco linhas de código. Talvez 6 ou 7, dependendo de como você conta o loop for
no template.
Modelos podem ser lidos a partir de um nome de arquivo, um array de linhas, um identificador de arquivo (incl. stdin) ou uma string como neste exemplo.
Você pode fazer cálculos, pesquisas de tabela, consultas de banco de dados (por exemplo, com o módulo DBI
), extração de dados de um arquivo CSV, buscar e processar a saída de sub-rotinas e programas externos e mais dentro de um modelo. Qualquer coisa que você possa fazer com perl
.
Para variáveis escalares simples, tudo o que você precisa fazer é incorporar o nome da variável no modelo dentro de chaves (por exemplo, com a variável $foo
, use {$foo}
). Para matrizes, hashes e cálculos, etc., você precisaria incorporar algum código perl
no modelo.
Aqui está uma versão que lê um nome de arquivo de modelo e um nome de arquivo de variável de configuração dos dois primeiros argumentos na linha de comando:
(em um sistema debian, isso requer que os pacotes libconfig-simple-perl
e libtext-template-perl
sejam instalados)
#! /usr/bin/perl
use strict;
use Text::Template;
use Config::Simple;
# create the Text::Template object ($tt)
my $tt = Text::Template->new(TYPE => 'FILE', SOURCE => $ARGV[0]);
# read the config file into the '%vars' hash.
my $cfg = new Config::Simple();
$cfg->read($ARGV[1]);
my %vars = $cfg->vars();
# strip "default." from key names.
%vars = map { s/^default\.//r => $vars{$_} } keys(%vars);
# fill in the template
my $text = $tt->fill_in(HASH => \%vars);
print $text;
NOTA: o script precisa remover default.
do início de cada nome de chave hash porque o arquivo de configuração é muito parecido com um arquivo .INI
e pode ter [sections]
como eles. Qualquer variável de configuração que não esteja em uma seção é considerada na seção default
. Escrever o modelo com variáveis como {$default.PUBLIC_INTERFACE}
seria entediante, então a solução é corrigir as chaves do %vars
hash.
BTW, estes% de[sections]
semelhantes a .INI não são necessariamente um problema. É possível fazer bom uso deles em um modelo. Mas o prefixo default.
é inútil e irritante quando usado com Text::Template
.
De qualquer forma, com este arquivo de modelo:
$ cat iptables.tpl
-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 80 -d {$PUBLIC_IP} --syn -j ACCEPT
{
my @o=();
foreach $a (@ADMIN_IPS) {
push @o, "-A INPUT -i $PUBLIC_INTERFACE -p tcp -m tcp --dport 22 -d $PUBLIC_IP -s $a --syn -j ACCEPT";
$OUT .= join("\n",@o);
}
}
-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 443 -d {$PUBLIC_IP} --syn -j ACCEPT
OBSERVAÇÃO: esse modelo foi um pouco melhor, pois usa uma matriz ( @o
) e join()
para evitar a adição de novas linhas indesejadas - você percebeu como "trapaceei" em $tstr
na primeira versão, adicionando um mais nova linha de modo que as linhas de saída da matriz estivessem em um parágrafo separado?
e este arquivo contendo as variáveis:
$ cat iptables.var
PUBLIC_INTERFACE=eth0
PUBLIC_IP=8.8.8.8
ADMIN_IPS=8.8.10.1, 8.8.10.2, 8.8.10.3
Esse arquivo funcionaria exatamente da mesma maneira se tivesse inserido [default]
como a primeira linha.
Além disso, ao contrário da maioria das formas de arquivos .ini, este tem uma maneira muito fácil de definir variáveis de matriz: basta separá-las com uma vírgula, com espaços em branco extras opcionais, exatamente o que você pediu em sua pergunta. p>
BTW, o espaço em branco é ignorado nas definições de variáveis, a menos que você as coloque entre aspas. Veja man Config::Simple
para mais detalhes sobre o (s) arquivo (s) de configuração.
Execute assim:
$ ./alexis.pl iptables.tpl iptables.var
-A INPUT -i eth0 -p tcp -m tcp --dport 80 -d 8.8.8.8 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.1 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.2 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.3 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 443 -d 8.8.8.8 --syn -j ACCEPT
É intencionalmente minimalista (ou seja, muito rápida e suja, por exemplo, nem sequer tenta validar a existência dos arquivos) e há muitas maneiras de melhorar, mas é um exemplo totalmente funcional de como fazer o básico trabalho.