Generate Text From Templates, Scripts and CSV Data

Posted on March 6th, 2008 in Tech Tips by gmendoza

If you are working on a project requiring that you generate hundreds or thousands of similar but unique text files, you probably will be looking for some type of task automation process. For example, a recent project required that we configure hundreds of Cisco switch configuration files, each being uniquely individualized with IP addresses, OSPF configurations, random passwords, etc. We also needed to configure an import data file for over a thousand DHCP ranges, obviously not something we felt like doing by hand.

For tasks like these, the best tools of the trade are text file templates that represent what the finished product should look like, spreadsheets for basic math and to create import data in the form of CSV’s (comma-separated values), and GNU/Linux shell scripting to pull it all together.

The process can be summarized as follows:

1. Use a template text file and identify the parts you wish to customize with easy to recognize variables.

2. Generate unique data in the CSV format that will serve to populate the template.

3. Run a Linux shell script that performs a search and replace function against the template, and outputs new text files with the values obtained from the CSV file.

In the following example, we will work with a basic Cisco switch configuration file that is condensed for brevity. Notice, we clearly label text within the template that we will use to search for and replace with values obtained from the CSV data. These variables are VAR_HOSTNAME, VAR_IPADDR, VAR_NETMASK, VAR_OSPF_KEY, VAR_TACACS_KEY.

Sample Template: import-template.txt

!
hostname VAR_HOSTNAME
!
interface Vlan100
ip address VAR_IPADDR VAR_NETMASK
ip ospf message-digest-key 1 md5 VAR_OSPF_KEY
!
tacacs-server host 1.1.1.1
tacacs-server key VAR_TACACS_KEY
!

The CSV file in our example is organized as follows:
Field 1=”Host name”, Field 2=”IP address”, Field 3=”Subnet Mask”, Field 4=”OSPF key”, and Field 5=”TACACS key”

Sample Data CSV File: import-data.txt

switch-01,10.1.1.1,255.255.255.0,ospf-key-1,tacacs-key-1
switch-02,10.1.1.2,255.255.255.0,ospf-key-2,tacacs-key-2
switch-03,10.1.1.3,255.255.255.0,ospf-key-3,tacacs-key-3
switch-04,10.1.1.4,255.255.255.0,ospf-key-4,tacacs-key-4
switch-05,10.1.1.5,255.255.255.0,ospf-key-5,tacacs-key-5

Here’s how we pull all of this together using a Linux shell script. First, we define the location of our CSV import file. Next we run a “for loop” that reads the CSV line by line, assigning each data value to a variable name. Lastly, the script concatenates the template, replacing each defined search result with values from the CSV, and sends the output to individualized text files. These text files are appropriately named according the unique value, which in this case is the switches host name.

Import Shell Script: import-script.sh

#!/bin/bash
IMPORT="./data/import-data.txt"
TEMPLATE="./data/import-template.txt"
 
for i in `cat ${IMPORT}`
do
VAR_HOSTNAME=`echo $i | awk -F, '{print $1}'`
VAR_IPADDR=`echo $i | awk -F, '{print $2}'`
VAR_NETMASK=`echo $i | awk -F, '{print $3}'`
VAR_OSPF_KEY=`echo $i | awk -F, '{print $4}'`
VAR_TACACS_KEY=`echo $i | awk -F, '{print $5}'`
cat $TEMPLATE | sed -e s/VAR_HOSTNAME/$VAR_HOSTNAME/g \
-e s/VAR_IPADDR/$VAR_IPADDR/g \
-e s/VAR_NETMASK/$VAR_NETMASK/g \
-e s/VAR_OSPF_KEY/$VAR_OSPF_KEY/g \
-e s/VAR_TACACS_KEY/$VAR_TACACS_KEY/g \
| tee ./output/$VAR_HOSTNAME.txt 1>/dev/null
done

Step By Step:

To follow along, you can download the above mentioned files, or copy and paste them from the text above. Place the import-data.txt and import-template.txt files in a new directory called “data”, create a directory called “output”, and make the import-script.sh file executable. The final step of course is to run the import script. Here is a step by step to get you going.

$ wget http://www.savvyadmin.com/download/import-script/import-script.sh
$ wget http://www.savvyadmin.com/download/import-script/import-data.txt
$ wget http://www.savvyadmin.com/download/import-script/import-template.txt
$ mkdir data output
$ mv ./import-template.txt ./import-data.txt ./data/
$ chmod 755 ./import-script.sh
$ ./import-script.sh

After you run the script successfully, list the “output” directory to see the newly created files.

$ ls -l output/
total 20
-rw-r--r-- 1 gmendoza gmendoza 184 2008-03-06 21:33 switch-01.txt
-rw-r--r-- 1 gmendoza gmendoza 184 2008-03-06 21:33 switch-02.txt
-rw-r--r-- 1 gmendoza gmendoza 184 2008-03-06 21:33 switch-03.txt
-rw-r--r-- 1 gmendoza gmendoza 184 2008-03-06 21:33 switch-04.txt
-rw-r--r-- 1 gmendoza gmendoza 184 2008-03-06 21:33 switch-05.txt

The contents of the first file in the example can be viewed as follows. Note that all the variables from the template have been replaced appropriately by the data from the CSV.

$ cat output/switch-01.txt
!
hostname switch-01
!
interface Vlan100
ip address 10.1.1.1 255.255.255.0
ip ospf message-digest-key 1 md5 ospf-key-1
!
tacacs-server host 1.1.1.1
tacacs-server key tacacs-key-1
!

I certainly hope this gives you some great ideas on how to make effective use of Linux scripts to make large amounts of work into manageable tasks. Comments and questions are welcome.

3 Responses to 'Generate Text From Templates, Scripts and CSV Data'

Subscribe to comments with RSS or TrackBack to 'Generate Text From Templates, Scripts and CSV Data'.

  1. blacky said,

    on March 8th, 2008 at 4:19 am

    If the number of variables needed increases, the sed solution can
    become quite unwieldy. Also, having different sets of defined
    variables isn’t quite as easy. Finally, if you have many switches
    (and many admins), it may be easier to have one file per switch
    to facilitate the use of a version control system. To this end,
    using the C preprocessor (or a similar macro system) may be
    useful.

    For example:


    $ cat import-template.txt
    !
    hostname HOSTNAME
    !
    interface Vlan100
    ip address IPADDR NETMASK
    ip ospf message-digest-key 1 md5 OSPF_KEY
    !
    tacacs-server host 1.1.1.1
    tacacs-server key TACACS_KEY
    !

    $ cat switches/switch-01
    #define HOSTNAME switch-01
    #define IPADDR 10.1.1.1
    #define NETMASK 255.255.255.0
    #define OSPF_KEY ospf-key-1
    #define TACACS_KEY tacacs-key-1
    $ cpp -P -include switches/switch-01 import-template.txt

    !
    hostname switch-01
    !
    interface Vlan100
    ip address 10.1.1.1 255.255.255.0
    ip ospf message-digest-key 1 md5 ospf-key-1
    !
    tacacs-server host 1.1.1.1
    tacacs-server key tacacs-key-1
    !

  2. gmendoza said,

    on March 9th, 2008 at 9:46 am

    Hey there, Blacky. Thanks for contributing again.

    The scenario I had in mind was purely for short-term time savings through bulk processing. The whole point of using a CSV is that it’s easy to import and parse thousands of records, as each iteration of the for-loop only has to process a single line to obtain the definitions separated by a delimiter. For this type of task, creating a CSV is easier to generate than cpp precompiler directives for subsitution, because the latter as you pointed out requires not only multiple lines for the definitions, but multiple files to serve as the import data.

    However, what I really like about your suggestion is that if the intention is to maintain a version control system, individualizing the import data as a template of it’s own would be quite beneficial! Plus, you have the added benefit of simply being able to generate a single configuration from your cpp template, rather than having to create a new CSV for the job.

    Now of course, I would still recommend using the method mentioned in the article to both generate and populate the cpp templates to get things started. But for continual maintenance of such text, your suggestion is certainly one that readers should look into.

    Thanks again.

  3. Larry said,

    on March 10th, 2008 at 11:23 am

    Sweet scripts, not that it’s much use to me.

Post a comment