Here's a perl solution using Text::Template. The script reads the template from a string variable ($tstr), performs all the replacements (including some embedded perl code to loop over the @ADMIN_IPS array, and then prints out the result:
(on a debian system, this requires the libtext-template-perl package to be installed)
#! /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;
Output:
-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
For most light templating like this, scalar (single-value) variables and the occasional list (aka array) or hash (aka associative array) is all you need.
The above script can be adapted endlessly for all sorts of similar jobs - just change the template and the $vars hash reference. And it's not at all difficult to, e.g., load the template from one file and the variables (scalars and arrays) from another, so you could have a tiny re-usable script that takes two file arguments (template and vars).
Apart from the template itself and the $vars setup there's only about 5 lines of code. Maybe 6 or 7, depending on how you count the for loop in the template.
Templates can be read from a filename, an array of lines, an already opened file-handle (incl. stdin) or a string as in this example.
You can do calculations, table lookups, database queries (e.g. with the DBI module), extraction of data from a CSV file, fetch and process the output of subroutines and external programs, and more within a template. Anything you can do with perl.
For simple scalar variables, all you need to do is embed the variable name in the template inside curly brackets (e.g. with variable $foo, use {$foo}). For arrays and hashes and calculations etc you'd need to embed some perl code in the template.
Here's a version that reads in a template filename and a config variable filename from the first two args on the command line:
(on a debian system, this requires the libconfig-simple-perl and libtext-template-perl packages to be installed)
#! /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;
NOTE: the script needs to strip default. from the beginning of each hash key name because the config file is very much like a .INI file and can have [sections] just like them. Any config variables not in a section are presumed to be in the default section. Writing the template with variables like {$default.PUBLIC_INTERFACE} would be tedious, so the solution is to fix the keys of the %vars hash.
BTW, these .INI-like [sections] aren't necessarily a problem. It is possible to make good use of them in a template. But the default. prefix is just pointless and annoying when used with Text::Template.
Anyway, with this template file:
$ 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
NOTE: this template is slightly improved as it uses an array (@o) and join() to avoid adding unwanted extra newlines - did you notice how I "cheated" in $tstr in the first version by adding one more newline so that the array output lines were in a separate paragraph?
and this file containing the variables:
$ 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
That file would work exactly the same if it had [default] inserted as the first line.
Also, unlike most forms of .INI files, this one has a very easy way of defining array variables: just separate them with a comma, with optional extra whitespace, which is exactly what you asked for in your question.
BTW, whitespace is ignored in the variable definitions unless you put it in quotes. See man Config::Simple for more details about the config file(s).
Run it like this:
$ ./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
It's intentionally minimalist (i.e. very quick and dirty, e.g. doesn't even try to validate the existence of the files) and there are many ways it could be improved, but it's a fully-functional example of how to do the basic job.
perl'sText::Templateor module or perl'sTemplate Toolkit) instead of re-inventing the wheel. Even just looking at the docs for such tools will give you ideas on how to implement things that you thought were impossible or too hard, or that had never occurred to you before.