Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Re: RFC: beginner level script improvement

by roboticus (Canon)
on Sep 20, 2013 at 00:40 UTC ( #1054924=note: print w/ replies, xml ) Need Help??


in reply to RFC: beginner level script improvement

georgecarlin:

Before I go into one of my long, meandering list of things, I'd like to say:

  • Don't get upset by the bits I write here.
  • These are opinions. They're my opinions, so of course at least half of them are right. Though we might disagree about which half that may be.
  • Congratulations! Your indentation style doesn't suck! Meaning that (a) you *use* indentation, (b) and it's consistent. Of course, it's not the best style, but that changes with the weather.

On a more serious note: By putting your code up here for review, I think you already have the right attitude. Always wanting to improve how you do things is a great skill for a programmer to have.

OK, then, on to the bitching! I've put it in <readmore> tags so people used to my occasional long and meandering nodes can skip it. A brief summary for them: Naming, whitespace, short subroutines! Possibly a bit more, I don't remember what I wrote, and I hate this keyboard.

Random notes as I read your code.

Chunk 1

There's too much "decoration". A common beginner habit is to spend a lot of time on formatting large comment blocks and such. It's not a terrible thing, it's just something I see far too often. Some decoration can be nice and help organization. But it's easy to go overboard.

End blocks are pointless. When you have a large heading on each section, it seems pointless to have an equally large end marker--especially since the next item will either be another large heading or the end of the file. Both will be good visual cues to the end of the previous section.

Names should be clear and meaningful. printversion, printtmpl: Neither of them actually print anything. Each simply returns a text string. A name should be suggestive: that's the most efficient method of documenting your code. You don't need to write many comments when your code clearly describes itself.

Digression 1: Many people will tell you to spell out everything in full. I think they're nuts. A proper set of common abbreviations go a long way to making the code simpler to read. Examples: FName means file name, FH means file handle, rpt means report, db means database, etc. If you're consistent--and you should be--the code can be easier to read.

Neither of your printversion, printtmpl functions actually print anything. Each simply returns a string. Rather than having a function for each string you need to retrieve, I'd suggest putting your text strings into a hash at the beginning, and provide a function to retrieve the one you want:

my %Templates = ( header=>q{ #### HEADER #### # yadda yadda yadda ################ }, config=>q{ #### CONFIG #### # Sets the frobnitz #DEFINE FROBNITZ=Blevin ################ }, . . . ); sub get_template { my $tpl_name = shift; die "Template '$tpl_name' doesn't exist!" unless exists $Templates{$tpl_name}; return $Templates{$tpl_name}; } # Now you can use it like: print get_template('header');
Chunk 2

In your help function, breaking your text up into multiple print statements complicates things more than necessary. Try using a single "here document":

sub help { my $NAME = $_[0]; . . . $NAME mangling . . . print <<HELP_TEXT; SECTION-USAGE: $0 blah blah blah ... as much text as you'd like. When you're done, just make sure you end it with your here-document terminator: HELP_TEXT }

Speaking of your help function, it doesn't look like your help function returns a useful value, so I simply removed it.

Your sani^Hatize function brings up a pet peeve: It's spelled incorrectly. When you name something that's used outside of your function, you *really* should spell it correctly. When a system grows enough, changing the names can be difficult to coordinate, so frequently they don't get corrected.

Then peepul wil haff to contsanly tyep teh saem mispelings evere tim yoo yoose it. And they will curse your name. And make fun of you.

The other problem with the sani^Hatize function is that it doesn't suggest what it *actually* does. Sure the word made sense when you wrote it. But a few months from now when you're tracking down a weird bug and you call sani^Hatize, you'll have to stop and look at the code to figure out what sani^Hatize means in this context.

Looking at the function, it simply performs a set of replacements with a funky hardcoded bit. I can't think of a good name for the function, which makes me think that you may be approaching the problem sideways.

Except for the hardcoded bit, I'd suggest translate, replace or similar for the function. But you can't use those as the basis for a name with that hardcoded bit unless you start adding adjectives to your function name: translate_with_funky_bit(). I'd suggest splitting out the general-purpose multiple_string_replace function from the do_funky_escape_code() bit. Then you could reuse the replacer if/when necessary. You could write a function that takes your list of things to replace and transform them into the list of things used for the replacer function.

Imagine my surprise when I started looking at the taint function: It's basically the multiple_string_replace function.

Next, gettime. For some reason, you don't mind a simple operation to convert the year to the correct value, but you use an array to fix up the month. Also, you're creating new variables instead of using the ones you have. If I were going to write it (instead of using a module or strftime), I'd do it more like:

sub gettime { my ($sec,$min,$hr,$day,$mon,$yr,$dayOfWeek) = localtime(); # Adjust the values to the values humans expect: $yr+=1900; $mon++; return "$weekDays[$dayOfWeek] $mon/$day/$yr $hr:$min:$sec"; }

I abbreviated the variable names because they're used in a very small area, and the abbreviations are obvious in context. I ignored the unused return values from localtime, since they aren't used. I also reused $yr and $mon to hold the "adjusted" values.

Your parsetempdb function does too much in one place, reducing any potential reuse. (I already mentioned naming, so I won't beat that horse any further.) The function has several interesting operations: Parsing the DB file may be useful by itself, so I'd split that out into a subroutine. Generating a report from the data structure could also be useful, so I'd split that out, too.

So you could wind up with something like:

sub print_tempDB_report_from_file { my ($DB_FName, $RPT_FName) = @_; my $tempDB = parse_db_file($DB_FName); my $report = generate_db_report($tempDB); open my $OFH, '>', $RPT_FName or die "meaningless death!"; print $OFH $report; } sub parse_db_file { my $DB_FName = shift; . . . code to parse DB File into data structure . . . return $tmpDB; } sub generate_db_report { my $tmpDB = shift; my $text; . . . code to turn data structure into a string . . . return $text; }

The same can be said for sendmail: It does more than the name implies. Splitting it into smaller components would be useful. One of those components should be a bit of code to send mail--you could call it sendmail! (Sorry, I couldn't resist.)

The Main Code

I'm running out of steam, so I'll just note a couple of things:

You don't use enough whitespace. Your indentation style is fine (it's clean and consistent), you just don't use it often enough. Line breaks are good too. It can be difficult to read a "wall of code", so use whitespace to break things up into single "thoughts". As an example, the 'perform various sanity checks' block is too tightly packed, so it's harder to read than it must be. Put in a few line breaks and indents.

Your main code block is all coded straight through. You can break it into subroutines (even if they're used only once!) to "outline the code", making it easier for the reader to understand. It also helps limit the amount of code you need to read to fix an error.

# Startup process_command_line(); perform_various_sanity_checks(); my ($user,$passwd) = get_login_data($CONFIGFILE); set_static_configuration(); # Do the work

Ah ... you *did* do this a bit later in the code. Good.

OK, I've run out of gas. So I'll leave with one last bit: Your functions in this chunk are just *too big*. I expect that it's more of the same: You're trying to do too much in a single subroutine.

You'll find things easier to maintain and test if you keep your subroutines short and focused. Each individual component can be tested easily. But if you make a large do-all function, it's a *lot* harder to test, and if you make a change, you have to update more tests to ensure you hit all the possibilities.

Thanks for sharing. And please don't be offended at my random writings. After all, I'm just a nitpicky bastard anyway! ;^)

...roboticus

P.S. I don't remember what prompted me to write this, and it's far away from where it was. I didn't want to throw it away, so here ya go, an added bonus:

Roboticus' rule #1: If you always have to do something, you should *never* have to do it.

Many languages foist this stuff on us. Perl is one of the better ones. One example is the switch statement in C:

switch (c) { case 'A': do_this(); break; case 'B': do_that(); break; case 'C': do_this() && do_that(); }

Here, you *always* have to put in the break statement, or C will fall through to the next case. You should only have to do something explicit for the *NON*default case.


Comment on Re: RFC: beginner level script improvement
Select or Download Code
Re^2: RFC: beginner level script improvement
by georgecarlin (Acolyte) on Sep 20, 2013 at 11:54 UTC
    Thank you for taking this much time to help me out! As mentioned above I will implement the formatting and naming changes and then have a crack at trying to "modulize" the code more. On a sidenote I am neither upset, nor offended by people helping me improve, least of all when they spice it up with some good humor ;) I will post the revised version when I've made some progress.

      georgecarlin:

      With a name like that, I expected you to appreciate a bit of humor--or have thick skin. ;^)

      Be sure to /msg me when you post your revised code, so I don't miss it. I'll be happy to look it over again--time permitting.

      ...roboticus

      When your only tool is a hammer, all problems look like your thumb.

        I have looked into the guides and cpan docs as well as the multiple hints that were linked/referenced here and revised the first part of the script. If nothing else it looks a lot more structured and improved my understanding of some aspects of perl.

        Next up will be the connect parts although I'm not sure how to break down the code in those subs yet. I discovered that as is the parent waits indefinetly for the childs to report back which is bad, so I'm looking into signal handling and alarming to remedy that. Lastly I haven't used print here in this part because I didn't feel it was needed.

        So here is what I have revised so far. (the hashes that will be passed to most functions are still capitalized I will change that once development is complete, for now I find it convenient)

        Feedback/criticsm is very aprecciated unless you wish to wait for the full revisal.

        Not a lot changed in the template package

        package eod_templates_V2; use strict; use warnings; use Data::Dumper; use Exporter; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION = 1.13; @ISA = qw(Exporter); @EXPORT = (); @EXPORT_OK = qw(get_tmpl); %EXPORT_TAGS = ( ALL => [qw(&get_tmpl)], ); sub get_tmpl { my $request = $_[0]; my %templates = ( version => "same bla as before\n", config => 'same wall of text as before', ); return "$templates{$request}"; } 1;

        revised and additonal functions so far. I might consolidate the two substitute functions and just substitute depending on the order the array_refs are passed to the sub. Haven't thought that through yet though.

        EDIT after posting:
        - modified sub populate_config_hash as per jwkrahn's post
        - modified sub populate_config_hash to include call to below sub
        - added sub get_username_and_password_from_stdin
        - added sub get_username_and_password_from_file
        - added sub process_static_config_setting
        - modified sub get_config_from_file to call above subs

        package eod_functions_V2; use strict; use warnings; use Cwd 'abs_path'; use File::Find; BEGIN { ##Discover and use required modules automatically at compile t +ime my $ABS_PATH = abs_path($0); find(\&wanted, $ABS_PATH); sub wanted { if ( $_ eq "eod_templates_V2.pm"){unshift(@INC,$File::Find::di +r);} } } use Data::Dumper; use Exporter; use eod_templates_V2 qw(:ALL); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION = 1.10; @ISA = qw(Exporter); @EXPORT = (); @EXPORT_OK = qw(print_help_and_exit populate_static_hashes populate +_config_hash validate_command_line_args get_config_from_file); %EXPORT_TAGS = ( ALL => [qw(&print_help_and_exit &populate_static_hashes &populate_ +config_hash &validate_command_line_args &get_config_from_file)], ); ## BEGIN FUNCTIONS ################################################# +################################################ ## SUB HELP ######################################################## +################################################ sub print_help_and_exit { my $name = $_[0]; print " \nSECTION-USAGE: This script is highly versatile, make sure you familarize your +self with it properly, before using it\n All options are case sensitive. Option/Argument handling is un +ix standard.\n mandatory arguments: --readfile|-r read cli commands from file and perform t +hem on all CPEs (default = $name.cfg. see section config file for hel +p) --connection-proto|-c define the protocol to use to connect + to the devices. (ssh OR telnet. No default setting) optional arguments: --prompt-based-auth|-p provide username + password for devi +ce access when asked. (default = enabled) WARNING: Supercedes LOGIN details in config file y +ou provide! --file-based-auth|-f provide username + password for device + access via the config-file. (default = disabled) --outfile|-o expects filename of csv to write the outpu +t to. (default = $name.csv) --no-quiet|-n log to STDOUT instead of logfile (mandato +ry if --debug is enabled, default = quiet) --debug|-d activate debug-log-level WARNING: do NOT use + with multiple CPEsoutput is massive!!! (default = disabled) --help|-h print this help and exit --verbose|-v enable verbose feedback in outputfil +e. Depending on the executed commands this can be several hundred cha +racters!!! (default = disabled) --Version|-V display extenensive version information an +d exit\n advanced optional arguments (case sensitive): --sendmail|-s expects comma separated list of e-mail ad +dresses to send generated report to as argument (default = disabled) alternatively it can be invoked multiple times wit +h different addresses. (First occurence will always be TO, all others + CC) --max-connections|-m run script in forked mode (massively e +nhances performance) with the specified ammount of max child processe +s (1-25) (default = disabled) --tacacs|-t verify tacacs functionality on VPN-Hubsite +before attempting to process devices. will abort script execution if +tacacs service is unresponsive (default = disabled) \nSECTION-CONFIG-FILE: Keep in mind some devices handle commands case sensitive! The file that you provide will be reset to its defaults during + script execution!!! open the default config file $name.CFG with an editor of your +choice and study the instructions it contains. You may modify the default file or create a new one and pass i +t to the script with option -r Note: please report all unexpected behavior to ###. ty\n\n"; exit 0; } ### SUB create_data_structs ## DOES: populate data structs ########## +############################################ sub populate_static_hashes { my ($STATIC,$REPORT) = @_; %$STATIC = ( MODE_EN => "", MODE_CFG => "", MODE_WR => "", CHAR_DIS => "", CHAR_EN => "", CHAR_CFG => "", CMD_EN => "", CMD_CFG => "", CMD_WR => "", CHAR_NL => "", CHAR_INVALID => "", CHAR_PAGE => "", CMD_QUIT => "", CHAR_CONFIRM => "", CMD_CONFIRM => "", CMD_ROOTDIR => "", CMD_SKIP => "", CMD_SET_NO_PAGE => "", ); %$REPORT = ( UNREACHABLE => '0', OK => '0', NOK => '0', NOFEEDBACK => '0', ERROR => '0', ); } ### SUB populate_config ### Does: populate config hash with command +line arguments ################################# sub populate_config_hash { my ($CONFIG,$arrayref) = @_; my ($name,$prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$ +tacacs,$verbose,$header,$mailref,$path) = @{ $arrayref }; my $range = '1000000'; my $rand = int(rand($range)); ##generate random temp-db filename t +o allow for simultanous script execution #put first element in array as mail_to and all others as CC my $mailto = shift @{ $mailref }; my $mailcc = join '', @{ $mailref}; %$CONFIG = ( username => "", password =>"", verbose => "$verbose", quiet => "$quiet", hubsite => '1.1.1.1', log => "$name.log", lockfile => "$name.lock", tempdb => "$path/$rand.db", tacuser => "user", tacpass => "pass", cfg => "$configf", outcsv => "$outf", batchsize => "$batchsize", mail_to => "$mailto", mail_from => 'NMS-script@me.net', mail_sub => "", mail_cc => "$mailcc", mail_data => "", proto => "$proto", tacacs => "$tacacs", ); if ($prompt == 1){ get_username_and_password_from_stdin(\%$CONFIG); #if -p was se +lected, get username and password now otherwise when parsing config-f +ile } } ### SUB get_username_and_password_from_stdin ### DOES: gets username + and password from stdin ######################## sub get_username_and_password_from_stdin { my $CONFIG = $_[0]; eval { local $SIG{ALRM} = sub { die("timeout waiting for user input. +Aborting script execution\n") }; alarm 10; print "please enter device or tacacs username\n"; $CONFIG->{username} = <STDIN>; alarm 0; chomp $CONFIG->{username}; alarm 10; print "please enter device or tacacs password\n"; $CONFIG->{password} = <STDIN>; alarm 0; chomp $CONFIG->{password}; }; if ($@){ die($@); } } ### SUB get_username_and_password_from_file ### DOES: parses userna +me and password string + store in hash ######## sub get_username_and_password_from_file { my ($CONFIG,$line) = @_; my @temp = split(/\=/, $line); #split setting and value my $string = lc $temp[0]; $string =~ s/your-//; unless(! defined( $temp[1]) ){ #config file setting supersedes + prompt, unless its undefined $CONFIG->{$string} = $temp[1]; #wichever it was is now in +the CONFIG hash as a key(username or password) with the corresponding + value } } ### SUB validate_command_line_args ### DOES: perform various sanity + checks on command line arguments ############# sub validate_command_line_args { my ($name,$prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$ +tacacs,$verbose,$header,$maildst,$path) = @{ $_[0] }; if ( $header ){ print get_tmpl('version'); exit 0; } unless($proto =~ /ssh|telnet/i){ die("$0:ERROR: Connection protocol must be specified.\n"); } unless( -e $configf){ die("$0:ERROR: $configf not found in $path. +typo or missing path?\n");} if ($debug == 1 && $quiet == 1){ die("$0:ERROR: log-level=debug ca +n NOT be used in quiet mode to avoid massive logfiles!\n");} if ( $batchsize > 30 ){ warn("$0:WARNING: More than 25 simultanous connections are NOT + allowed!\nReducing Max_Connections to 25.\n"); $batchsize=25; }elsif ( $batchsize < 0 ){ warn("$0:WARNING: Negative Max_Connections are not permitted, +forking has been disabled.\n"); $batchsize = 0; } } ## SUB process_static_config_setting ### DOES: check if setting is v +alid and store in hash or discard ############# sub process_static_config_setting { my ($STATIC,$line) = @_; $line =~ s/DEFINE//; my @t=split(/=/,$line); #split statement into key => value pairs $t[0] =~ s/\s*//; $t[1] =~ s/\\//; if ( exists $STATIC->{$t[0]} ){ $STATIC->{$t[0]}=$t[1]; #key is supported, set value }else{ warn("$0:WARNING $t[0] is NOT a valid static configuration opt +ion and will be ignored!\n"); #key is not supported and will be ignor +ed } } ## SUB get_config_from_file ### DOES: process config file and set pop +ulate hashes accordingly ###################### sub get_config_from_file { my ($DATA,$CONFIG,$STATIC) = @_; my $count = 0; open(my $input, "<", "$CONFIG->{cfg}") or die("$0:ERROR unable to + read from $CONFIG->{cfg}. ERR: $?/$!"); while (<$input>){ chomp $_; if ( $_ =~ /your-username/i || $_ =~ /your-password/i ){ #this + line contains either the username or the password, it's irrelevant w +hich get_username_and_password_from_file($CONFIG,$_); next; } if ( $_ =~ m/^#/ || ! $_ =~ /[a-z]|[A-Z]/ ){ next; } #ignore +commented and empty lines, as well as password + username. if ( $_ =~ m/^DEFINE/ && $_ =~ /\=/){ #process config definiti +ons process_static_config_setting($STATIC,$_); next; } if ( $_ =~ m/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){ +3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):/ ){ #line contains a vali +d ipv4 IP addr, this is a device-commands set my (@metachars,@translations); populate_static_arrays(\@metachars,\@translations); $_ = substitute_escaped_metachars($_,\@metachars,\@transla +tions); #substitute escaped metachars to prevent config parser from i +nterpreting them my @temp = split(/,/, $_); #split line from config-file in +to command sub-sets, first one containing device IP as well my (@commands,@matches,$ip); for (my $i=0; $i < @temp; $i++){ #process each subset if ($i == 0){ my @temp1 = split(/:/, $temp[0]); #this is the fir +st subset, it contains the IP $ip = $temp1[0]; $count++; $temp[0] =~ s/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9 +][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)://; #remove IP + from subset } my @arr; if ($temp[$i] =~ /\=/ ){ #this subset contains pattern +-matching instructions my @temp2 = split(/\=/, $temp[$i]); #split subset +into further subsets, first containing the command, the rest are patt +erns $temp2[0] = substitute_placeholders($temp2[0],\@me +tachars,\@translations); #all splitting is done on this string, rever +t it back to its original form $commands[$i] = $temp2[0]; #this is the command for (my $c=1; $c < @temp2; $c++){ #these are all t +he patterns $temp2[$c] = substitute_placeholders($temp2[$c +],\@metachars,\@translations); ##undo prior substitutions $arr[$c] = $temp2[$c]; } $matches[$i] = \@arr; }else{ #this subset does not contain pattern matching +instructions $temp[$i] = substitute_placeholders($temp[$i],\@me +tachars,\@translations); ##undo prior substitutions $commands[$i] = $temp[$i]; $arr[$i] = "no-match-hook"; ##set flag for easy id +entification later on in the script $matches[$i] = \@arr; } } $$DATA{$ip}->{commands} = \@commands; $$DATA{$ip}->{matches} = \@matches; } } $CONFIG->{devices_count} = $count; if ( $count == 0 ) { die("$0:ERROR $CONFIG->{cfg} does not contai +n valid Device-instruction-sets\n");} close($input); open(my $input1, ">", "$CONFIG->{cfg}") or warn("$0:WARNING unable + to write to $CONFIG->{cfg}. CLEAR IT manually! ERR: $!/$?"); print $input1 get_tmpl("config"); #replace specified config file + with template for security reasons close($input1); while ( my($key,$value) = (each %$STATIC)){ unless(defined($value) && $value ne ""){ die("$0:ERROR: $key d +efinition missing in $CONFIG->{cfg}.Aborting Execution.\n");} #missin +g key definition, this can not be tolerated } } ### SUB populate_static_arrays ### DOES: populate static arrays meta +chars and translations ######################## sub populate_static_arrays { my ($meta_ref,$trans_ref) = @_; @{ $meta_ref } = (':','=','!',','); #declare config metachars @{ $trans_ref } = ('#00','#01','#02','#03') #declare bi-direction +al placeholders } ### SUB substitute_escaped_metachars ### DOES: substitute ctrl-chars + for escaped metachars in $LINE ############### sub substitute_escaped_metachars { my ($line,$meta_ref,$trans_ref) = @_; for (my $i=0; $i<@{ $meta_ref };$i++){ $line =~ s/\#\Q$meta_ref->[$i]/$trans_ref->[$i]/g; ## hardco +ded ESCSEQ => # } return $line; } ### SUB substitute_placeholders ### DOES: revert string to its origi +nal form priot to escapeing #################### sub substitute_placeholders { my ($line,$meta_ref,$trans_ref) = @_; for (my $i=0; $i<@{ $trans_ref };$i++){ $line =~ s/$trans_ref->[$i]/$meta_ref->[$i]/g; } return $line; } 1;

        main script so far

        #!/usr/bin/perl -w use strict; use File::Find; use Cwd 'abs_path'; BEGIN { ##Discover and use required modules automatically at compile t +ime my $ABS_PATH = abs_path($0); find(\&wanted, $ABS_PATH); sub wanted { if ( $_ eq "eod_templates_V2.pm" or $_ eq "eod_functions_V2.pm +"){unshift(@INC,$File::Find::dir);} } } use IO::Handle; use Expect; use Net::Telnet; use Data::Dumper; use Parallel::ForkManager; use Fcntl; use MIME::Lite; use Fcntl qw(:DEFAULT :flock); use Getopt::Long; Getopt::Long::Configure ("bundling"); use eod_templates_V2 qw(:ALL); use eod_functions_V2 qw(:ALL); my $path = Cwd::getcwd(); my $name = $0; $name =~ s/\.pl//; $name =~ s/\.\///; my $exitstate = 0; unless( ! @ARGV ){ $exitstate = Main(); }else{ print_help_and_exit($name); } exit($exitstate); sub process_command_line_args { #set default values for dynamic config settings my ($prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$tacacs +,$verbose,$header,@maildst) = (1,"$name.cfg",1,"$name.csv",0,0,"none" +,0,0); GetOptions ( 'file-based-auth|f' => sub { $prompt = 0;}, 'prompt-based-auth|p' => \$prompt, 'readfile|r=s' => \$configf, 'outfile|o=s' => \$outf, 'no-quiet|n' => sub { $quiet = 0; }, 'debug|d' => \$debug, 'Version|V' => \$header, 'max-connections|m=s' => \$batchsize, 'sendmail|s=s' => \@maildst, 'connection-proto|c=s' => \$proto, 'tacacs|t' => \$tacacs, 'verbose|v' => \$verbose, 'help|h' => sub { print_help_and_exit($name);} ) or print_help_and_exit($name); @maildst=split(/,/,join(',',@maildst)); #in case -s was invoked wi +th a comma separated list instead of multiple times my $mailref = \@maildst; my @args = ($name,$prompt,$configf,$quiet,$outf,$debug,$batchsize, +$proto,$tacacs,$verbose,$header,$mailref,$path); return \@args; } sub Main { #get user defined dynamic config settings and perform various sani +ty checks my $argref = process_command_line_args(); validate_command_line_args($argref); #all args check out now create and populate static hashes my (%STATIC,%REPORT); populate_static_hashes(\%STATIC,\%REPORT); #fill dynamic hash my %CONFIG; populate_config_hash(\%CONFIG,$argref); #retrieve config settings from file my %DATA; get_config_from_file(\%DATA,\%CONFIG,\%STATIC); return $exitstate; #will be set depending on outcome of actual dev +ice operations }

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://1054924]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (7)
As of 2014-08-28 03:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (255 votes), past polls