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

As some-one who has had NO formal programming training, I face this issue often. I have just written some code and tested it successfully. I manage to get perl to do what I need it to. What ALWAYS concerns me though, is that I am never confident in whether or not my code is "Best Practice" (for lack of a better expression). I mean are there better ways of doing certain things...? So here is what I have and would like comment on/correction for.

This code reads in the XML file

<?xml version="1.0"?> <!-- This is XML for the X-CallLogger --> <md_map> <md name="SRVONE"> <Sys>ARS</Sys> <Server>HDSrv</Server> <Schema>Sch1</Schema> <User>Demo</User> <Pass></Pass> <Update_Core>Y</Update_Core> </md> <md name="SRVTWO"> <Sys>ARS</Sys> <Server>HDSrv</Server> <Schema>Sch2</Schema> <User>Demo</User> <Pass></Pass> <Update_Core>Y</Update_Core> </md> <md name="SRVTHREE"> <Sys>ARS</Sys> <Server>HDSrv</Server> <Schema>Sch1</Schema> <User>Demo</User> <Pass>pass</Pass> <Update_Core>N</Update_Core> </md> </md_map>
It checks for each name attribute and then checks if a corresponding dir exists in the specified path. If a dir is not there is creates it.
#! /usr/bin/perl use strict; use warnings; use XML::Simple; my ($mdlist, @mds, @dirs, $key, $callout_loc); $callout_loc = "."; my $file = "./mdlist.xml"; my $xs1 = XML::Simple->new(); eval { $mdlist = $xs1->XMLin($file, forcearray => 1, keyattr => 'name' +) }; if ($@) { print "Problems with Loading XML - $@\n"; exit(); } &Get_MDs(); &Get_Dirs(); #Check if Dir created else create it. map { &checkdir } @mds; ###################################################################### +################## ## ## SUB ROUTINES ## ###################################################################### +################## #Get List of MDs sub Get_MDs { foreach $key (keys (%{$mdlist->{md}})) { push(@mds, $mdlist->{md}->{$key}->{'name'} . $key); } } #Get list of dirs sub Get_Dirs { if ( opendir( MDDIR, "$callout_loc" ) ) { while( my $dir = readdir( MDDIR ) ) { next if( ( "." eq $dir ) || ( ".." eq $dir ) ); push( @dirs, $dir ) if( -d "$callout_loc/$dir" ); } closedir( MDDIR ); } else { #Could not OPEN Dir print "Could not open $callout_loc to check for MD Directories +: $!\n"; exit (0); } } #Check if Dir Exists else Create. sub checkdir { print "Checking DIR for $_ ... "; undef my %dir_exists; for (@dirs) { $dir_exists{$_} = 1 } if (!$dir_exists{$_}) { print "Creating.\n"; mkdir ($_); } else { print " Exists\n"; } }

So Basically I have two questions:

Oh and File::Find was not used as I just could not get my mind around diretory depth issues...

-----
Of all the things I've lost in my life, its my mind I miss the most.

Replies are listed 'Best First'.
Re: Read XML, Create Dir if not exist
by seattlejohn (Deacon) on Feb 07, 2003 at 09:32 UTC
    Because Perl's motto is There's More Than One Way To Do It (TMTOWTDI), you'll probably find quite often that There's A Better Way To Do It Than You Could Think Of At The Time. But I think it's a good sign that you're willing to ask and to learn, and the code above looks like a good start.

    One key change I'd suggest is to set your subs up so that they accept parameters and return values rather than relying on global variables directly. This is good habit to develop for a lot of reasons, and it will definitely pay off as you start to develop produce larger programs. For instance, you might rewrite Get_MDs like this:

    sub Get_MDs { my ($mds) = @_; my @results; foreach $key (keys (%{$mds->{md}})) { push(@results, $mds->{md}->{$key}->{'name'} . $key); } return @results; }
    Then you'd invoke the revised sub with a call like this:
    my @mds = Get_MDs($mdlist);
    This tends to make your code much more reusable, because you can store input and output in arbitrary variables.

    It would also be a good idea to pass a directory name to checkdir explicitly, rather than relying on it to use the current value of $_.

    Second, you're using map in a way that's considered less than ideal. map's purpose is to perform a function on each element of a list and construct a list from the results. So a typical use of map might look something like this: my @squares = map {$_**2} @numbers; In your code above, you're simply throwing away the results; a better alternative would be to replace map with a for construct:

    checkdir($_) foreach (@mds)

    Finally, I'd be inclined to replace your print "error"; exit code blocks with a single die "error" statement. die will direct output to STDERR rather than STDOUT and will terminate the program with a non-zero exit code, both of which can be useful if you want to execute the program from within another script (Perl or shell or otherwise).

    Since die is a single command, it also lends itself to the following idiom:

    die "problem: $@" if $@;
    which is arguably more readable than the four lines it takes to do essentially the same thing following your eval, for example.

            $perlmonks{seattlejohn} = 'John Clyman';

Re: Read XML, Create Dir if not exist
by pike (Monk) on Feb 07, 2003 at 12:00 UTC
    You first read the whole XML file, then extract the 'name' attribute of the 'md' elements and then process them. This is perfectly valid, but given that you are interested only in a small part of the information, it is probably simpler to read the md elements and process the information (i. e. create the directories) as you go. XML::Twig is perfect for this kind of task:

    #!/usr/bin/perl -w use strict; use XML::Twig; my $callout_loc = "."; my $file = "./mdlist.xml"; #create the parser (twig) #and tell it what sub to call if it finds an 'md' element my $twig = new XML::Twig (twig_handlers => {md => \&process_md}); #parse the file #process_md will be called every time an 'md' element is found $twig->parsefile ($file); #that's it exit 0; sub process_md { my ($t, $md) = @_; #get the name attribute my $name = $md->att ('name'); #create the dir unless it already exists my $dirname = "$callout_loc/$name"; unless (-d $dirname) { print "Creating $dirname...\n"; mkdir $dirname or die "Could not create dir $dirname: $!\n"; } else { print "$dirname already exists!\n"; } }
    Much shorter, isn't it? And btw, I tested it, it works. HTH,

    pike

•Re: Read XML, Create Dir if not exist
by merlyn (Sage) on Feb 07, 2003 at 16:02 UTC
    As some-one who has had NO formal programming training, I face this issue often. I have just written some code and tested it successfully. I manage to get perl to do what I need it to. What ALWAYS concerns me though, is that I am never confident in whether or not my code is "Best Practice" (for lack of a better expression). I mean are there better ways of doing certain things...?
    Oddly enough, I can say literally the same thing. Even after a 25-year-long professional programming career.

    Welcome to the club. Those who say they know are those who never learn something new.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

(jeffa) Re: Read XML, Create Dir if not exist
by jeffa (Bishop) on Feb 07, 2003 at 13:47 UTC
    I don't have time to post a code example now (hopefully soon), but i just wanted to pop in and say check out XML::XPath. I believe it would be an excellent fit for this problem.

    24 hours later....

    What a day! Ok, here is some code for you ...
    use strict; use warnings; use XML::XPath; my $xp = XML::XPath->new(filename => 'mdlist.xml'); my @mds = map { $_->getAttribute('name') } $xp->findnodes('/md_map/md[@name]'); for my $dir (@mds) { # create $dir if applicable }
    And that's it - much 'simpler' than XML::Simple if you ask me. Encapsulating code into subroutines is usually the way to go, but sometimes it's overkill, especially with small Perl scripts like this one.

    One thing to note -- you are relying upon your script being executed from a particular directory in order for it to work. Not good. Instead, use absolute paths to the files:

    my $callout_loc = '/path/to/xml/stuff'; my $file = "$callout_loc/mdlist.xml";
    And so forth. Another point is that you might be doing too much error checking. I would try to create the directory even if it exists and pass that error message to the user instead:
    mkdir $dir or warn "$dir already exists: $!";

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)