footpad has asked for the wisdom of the Perl Monks concerning the following question:

I'm in the process of trying to clean up a script I wrote some time ago, one that has a number of different variable values.

As an exercise to sharpen some of my basic skills, I decided to write a simple configuration file parser. The idea, of course, being to move the variable data into a separate files that could be edited as needed.

Now, I am fully aware that CPAN provides the AppConfig module, among others. Normally, I'm a strong advocate for using CPAN whenever possible. In this case, however, there are a number of factors that lead to me this exercise:

  1. I wanted to do it.
  2. I am not the admin on the server that will eventually host the final script.
  3. The admin in question is reluctant to install additional modules (for lots of bad reasons that have been previously discussed).
  4. I am reluctant to cause a political scene (I have a bad enough reputation as it is).

Are these good reasons for not installing AppConfig? Of course not. Again, my primary purpose for doing this was to sharpen my skills. (Put another way, I'm fully aware that I'm reinventing a wheel, but I think I have good reasons for doing so.)

In any event, here's my current version and I would appreciate any feedback or suggestions.

#!/usr/bin/perl -w use strict; use File::Basename; my $cfgfile = "/datapath/filename.cfg"; my %settings = readConfigData( $cfgfile ); my $novalues = scalar( keys( %settings ) ); # Since we're done with the file name; reuse the var ( $cfgfile ) = fileparse( $cfgfile ); print "The $cfgfile file contains ", "the following settings and values:\n\n", map { " $_ = $settings{ $_}\n" } sort keys %settings; print "\n", "$novalues settings were found in all."; sub readConfigData # -------------------------------------------------------------- # Returns a hash containing setting names and values from the # configuration file. Note that the name of the configuration # file is passed as the first parameter. # -------------------------------------------------------------- { my $filename = shift; # name of the config file. my $filedata = ""; # holds a line read from the file my %settings = (); # holds results returned to caller my $cfglabel = ""; # holds name of a setting my $cfgvalue = ""; # holds value of a setting open( DATAFILE, "< $filename" ) or die "Can't Open $filename; reason: $!\n"; while ( <DATAFILE> ) { chomp; $filedata = trim( $_ ); # save line into var. # ignore comments and blank lines if ( ( substr( $filedata, 0, 1 ) eq '#' ) or ( $filedata eq "" ) ) { next; } else # it looks like data, so split it up, trim # white space and save it into the hash. { ( $cfglabel, $cfgvalue ) = split( /=/ ); $cfglabel = trim( $cfglabel ); $cfgvalue = trim( $cfgvalue ); $settings{ $cfglabel } = $cfgvalue; } } close( DATAFILE ) or die "Can't Close $filename; reason: $!\n"; return %settings; } sub trim # -------------------------------------------------------------- # Removes white space from either side of a value. Used in a few # places. # -------------------------------------------------------------- { my $value = shift; $value =~ s/^\s+//; # trim leading white space $value =~ s/\s+$//; # trim trailing white space return $value }

In this example script, I'm simply dumping the contents of the configuration file itself. Note that all values have been mangled to protect the guilty.

The data file itself is, as you can probably tell, loosely based on the Windows .INI format, though I don't include support for sections or for arrays. The depth of the support is fine for my current configuration needs.

An example .cfg file might be something along these lines:

HTMLHEAD = \data\htmlhead.html HTMLFOOT = \data\htmlfoot.html MAILHOST = mail.example.com MAILTO = <username\@example.com> MAILFROM = <sender\@example.com> MAILCOPY = <someoneelse\@example.com> LOCKTRYS = 10 DATAFILE = \data\counter.dat

You get the idea.

Thanks in advance...

--f

Replies are listed 'Best First'.
Re: Simple Configuration Parser
by chromatic (Archbishop) on Mar 11, 2001 at 05:24 UTC
    That's simple enough, I don't see where it could fail. It's possible to shorten things slightly, at least when skipping comments or whitespace:
    sub readConfigData { my $filename = shift; # name of the config file. my %settings; open( DATAFILE, "< $filename" ) or die "Can't Open $filename; reason: $!\n"; while ( <DATAFILE> ) { next unless /\S/; # skip lines of only whitespace next if /^\s*#/; # skip comments s/^\s+|\s+$//g; # trim whitespace in place my ( $cfglabel, $cfgvalue ) = split( /\s*=\s*/, $_, 2 ); $settings{$cfglabel} = $cfgvalue; } close( DATAFILE ) or die "Can't Close $filename; reason: $!\n"; return %settings; }
    Not only is it shorter, it's slightly more efficient. Additionally, the split will remove whitespace from after the key and before the value. It even now allows the value to contain an equal sign.

    Very Early Update: dws pointed out that I had forgotten a slash. See if you can figure out where!

Re: Simple Configuration Parser
by Trinary (Pilgrim) on Mar 12, 2001 at 11:01 UTC
    The way we do config files here (not sure how common the approach is) is to make the config file a hash...ie:
    { confval1 => 'foo', confval2 => { 'bar' => 'baz' }, # etc.. }
    You get the idea. You just snarf the file, eval it, and boom, you have a hash of config values.

    this has really helped us get stuff configurable quickly, and I don't really see many drawbacks, as long as you take care who can read/write the conf.

    Comments?

    Trinary

      I do something similar. What I really like about this approach is that I can have a path where configuration files are kept. There is no need to stop at the first one. Instead you pick them up in turn, later ones override the first.

      This is nice because it allows you to have later files override only what they need to. For an example of how to use this consider the case where your path goes through a CVS directory and winds up out of CVS. Then developers can try changing a few defaults here and there without changing files in CVS and running the risk of checking in something that was meant to be part of a private test...