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

Hello fellow monkeys, I am new to perl scripting and I have spent 6 hours trying to write a script that would prase a nagios services.cfg file. One variable in the services.cfg file called "services_description" is missing in many definitions, some do have them set, the definition always start with definition{ and ends with }. I was doing line by line with many if's to go thru the 30 thousand lines text file,but still not working :( Anyways here is the example of the file...
define service{ use sam_win_cpu_load host_name buo.va.com contact_groups buo_admins } define service{ use generic-service host_name buo.va.com service_description MS SQL Server check_command check_service_state!MSSQLSERVE +R!public contact_groups buo_admins }
When the service_definition dosent exist I have a subroutine that parses the use and just remove the sam from it and returns... (that is the easy part) Should I put entire file into an array, split it? Any ideas are very very welcome

p.s. i can paste my ugly code if anyone wants... thanks,

Replies are listed 'Best First'.
Re: How to parse a text file
by jbt (Chaplain) on Aug 10, 2009 at 13:11 UTC
Re: How to parse a text file
by jethro (Monsignor) on Aug 10, 2009 at 13:42 UTC

    And if jbts excellent advice didn't help you, then yes, posting of the code is welcome and even suggested in How do I post a question effectively?. Especially when your code is ugly, monks can do a lot for code beauty ;-).

    But please don't use pre-tags, use <p>-tags for text and <code>-tags for code and data.

      I checked the Nagios::Object::Config; but I dont think its for me, since I just need to parse a .cfg file and I am not even in the nagios server...

      If I just could find an element "{" inside the array mark its index find the end element "}" then split its values...

      someone must have done simillar to these, I have searched for hours about arrays in perl but still no good...
      my $use = ""; my $sd = ""; my $count = 1; my $bset= 0; # if new SD was set,change to 1, 0 means it was not se +t. my $bend = 0; # means definition is ended } was found 1, 0 means sti +ll needs to close definition. open (TEXT_FILE, "<test.txt"); open (MYFILE, '>services2.cfg'); while ($line = <TEXT_FILE>) { print MYFILE $line; print $count ." - " . $line; if ($line =~ m/use/) { $bset = 0; $bend = 0; print "-----Reseting bset and bend\n"; $host = <TEXT_FILE> ; print MYFILE $host; $nextline3 = <TEXT_FILE> ; $nextline4 = <TEXT_FILE> ; $nextline5 = <TEXT_FILE> ; $nextline6 = <TEXT_FILE> ; $use = $line; $use =~ s/use//; $use = trim($use); $host =~ s/host_name//; $host = trim($host); print $count . ". ", color("white") . $host ."\n", color("res +et"); # Should check if any of the lines already have service_description or + } #could use a loop foreach ($nextline(i))...i++ #Line 3 if ($nextline3 =~ m/}/ ) { #service_description does not exist and was not set, we are in the las +t line, we add SD and } and define service of next printf ("(L3=}) Adding SD and }\n"); $sd = sdparse($use); print color("yellow"), "\n-->NEW SD: ". $sd ."\n", color(" +reset"); print MYFILE " service_description ". $sd."\n}\n\n +define service{\n"; $bset = 1; $bend = 1; } if ($nextline3 =~ m/service_description/ && $bend eq 0) { print "(L3) SD here. \n"; print MYFILE $nextline3; $bset = 1; } if ($nextline3 !~ m/service_description/ && $bset eq 0 && $ben +d eq 0) { print MYFILE $nextline3; } if ($nextline4 =~ m/}/ && $bend eq 0 && $bset eq 0) { #service_description does not exist and was not set, we are in the las +t line, we add SD and } and define service of next printf ("(L4=}) Adding SD and }\n"); $sd = sdparse($use); print color("yellow"), "\n-->NEW SD: ". $sd ."\n", col +or("reset"); print MYFILE " service_description ". $sd."\n}\n +define service{\n\n"; $bset = 1; $bend = 1; } if ($nextline4 =~ m/service_description/ && $bset eq 0 && $ben +d eq 0) { print "(L4) SD here. \n"; print MYFILE $nextline4; $bset = 1; } if ($nextline4 !~ m/service_description/ && $bend eq 0 ) { if ($bset eq 0) { print "(L4) Is not SD,and SD not found so far keep goi +ng. \n"; print MYFILE $nextline4; } if ($bset eq 1) { print MYFILE $nextline4; } } #Line 5 etc....

        Generally your program would have been better written as a Finite-state_machine, further examples here and here. Basically you read each line and decide in a big switch or if-then-else construct, what the next state should be. That is something you are already doing a little with $bset and $bend, but then you try to read and parse 4 lines in one go and it gets complicated and you have to write a lot of code more than once.

        A state machine helps to prevent this. You have only one place where you read a line from the file. Then depending on state and line you do something and change the state

        In your case there would be one state "outside define service" and one state "inside define service" (you can use a number to denote state but you should document what that number means). If you are in state "inside define service" there are three cases:

        1) the line begins with "service_description": You don't print the line but push the line into an array. Set a flag that tells you you have seen it

        2) the line begins with some other text: You don't print the line but push the line into an array.

        3) the line is a '}': look for the 'use' line in the array and remove the sam if the flag is not set. Print the array and set the state to "outside define service".

        In the "outside define service" state you just look for a "define service" line and if you find one, change state and clear the array and flag

        The program would look something like this:

        ... my $state="outside"; my @servicelines; my $desc_flag; #is 1 if description found while ($line = <TEXT_FILE>) { if ($state eq "outside") { print MYFILE $line; if ($line=~/^\s*define service/) { $state="inside"; @servicelines=(); $desc_flag=0; } } else if ($state eq "inside") { if ($line=~/^\s*}/) { @servicelines= removesam(@servicelines) if ($desc_flag); print @servicelines; print $line; $state="outside"; } elsif ($line=~/^\s*service_description/) { push @servicelines, $line; $desc_flag=1; } else { push @servicelines, $line; } } } ...

        Missing from above is the sub removesam, also any error checks, to warn you if the file you are parsing is not conforming to expectations. For example a '}' line in state "outside" should print a warning. Also as a general advice you should add "use warnings;" and "use strict;" at the start of your program even though strict mode is slightly more work for you, it prevents errors.

        Hey!

        Now that you're listening, please EDIT both your parent and grandparent nodes, replacing <pre>...</pre> tags with <p>...</p> and <c>...</c> tags. Your present markup borks the display for most Monks.

        As you were told by message, if you need more on appropriate markup here, see Markup in the Monastery.

Re: How to parse a text file
by bichonfrise74 (Vicar) on Aug 10, 2009 at 23:31 UTC
    This is what I tried based on your question. So, I specified a new delimiter, in the case, the '}', and then I just check if 'service_description' is present or not, and so on.

    Let me know if I'm missing something.
    #!/usr/bin/perl use strict; local $/ = '}'; while( <DATA> ) { if ( /\s+service_description\s+/ ) { next; } else { s/(use\s+)sam(\w+)/$1$2/; } print; } __DATA__ define service{ use sam_win_cpu_load host_name buo.va.com contact_groups buo_admins } define service{ use generic-service host_name buo.va.com service_description MS SQL Server check_command check_service_state!MSSQLSERVE +R!public contact_groups buo_admins }
Re: How to parse a text file
by Marshall (Canon) on Aug 12, 2009 at 07:16 UTC
    I wasn't exactly sure what you wanted.
    The code below parses the DATA and creates an Array of Hash.
    That @AoH is then "dumped" to STDOUT.
    #!/usr/bin/perl -w use strict; use Data::Dumper; my @AoH; while (<DATA>) { make_AoH_entry() if (/^define service{/); } print Dumper(\@AoH); sub make_AoH_entry { my %hash; while ( (my $line=<DATA>) !~ /^}/ ) { next if ($line =~ m/^\s*$/); my ($name, $value) = ($line =~ m/\s*(\w+)\s+(.*)$/); $hash{$name} = $value; } push (@AoH, \%hash); } #================== #Prints: #$VAR1 = [ # { # 'use' => 'sam_win_cpu_load', # 'host_name' => 'buo.va.com', # 'contact_groups' => 'buo_admins' # }, # { # 'check_command' => 'check_service_state!MSSQLSERVER!publi +c', # 'service_description' => 'MS SQL Server', # 'use' => 'generic-service', # 'host_name' => 'buo.va.com', # 'contact_groups' => 'buo_admins' # } # ]; __DATA__ define service{ use sam_win_cpu_load host_name buo.va.com contact_groups buo_admins } define service{ use generic-service host_name buo.va.com service_description MS SQL Server check_command check_service_state!MSSQLSERVE +R!public contact_groups buo_admins }
Re: How to parse a text file
by dwm042 (Priest) on Aug 10, 2009 at 19:32 UTC
    I have to admit, I like jethro's suggestion of a state machine and if I could vote for it twice, I would.