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

Hello everyone,

I need to remove certain multi-line entries from files with a format like this:
[reports] yearlystore = quarterlystore = monthlystore = weeklystore = dailystore = [server1] logfilepath1 = F:\Logfiles\ZWire\Zwire4\split-0\ex*.log servername = Zwire4 logfilepassword = logfileusername = id = a104136320003954 [server2] servername = Zwire6 logfilepath1 = f:\logfiles\zwire\zwire6\split-0\ex*.log id = a104136346513171 logfilepassword = logfileusername = [server3] id = a104136351461256 logfilepath1 = f:\logfiles\zwire\zwire7\split-0\ex*.log logfilepassword = servername = zwire7
There are many types of
[header] item = item2 = item3 =
entries in these files. I am only concerned with the
[server1] logfilepath1 = F:\Logfiles\ZWire\Zwire4\split-0\ex*.log servername = Zwire4 logfilepassword = logfileusername = id = a104136320003954
type of entries. I need to remove everything from [serverX] to the next [serverX+1] (but not including it). I will be removing entries based on the contents of the servername = line.

Making this even more complex, I will have to re-number the [serverX] entries if one is removed, so that they remain in sequential order!

I've tossed around doing something like this:
while (<IN>) { if (/\[server(\d)\]/) { do { $throwaway = <IN>; } until { $throwaway = /\[server(\d)\]/ } +; } else { push @output, $_; } print OUT @output;
Of course, this doesn't help me when I'm only looking for a particular server, or the re-numbering.

Perhaps something more like this?
undef $/; $file = <IN>; # to slurp whole file at once $file =~ s/ # some complex regex //m;
All advice appreciated from the wise monks!

Cheers,
ibanix

$ echo '$0 & $0 &' > foo; chmod a+x foo; foo;

Replies are listed 'Best First'.
•Re: Searching for and removing multi-line file entries
by merlyn (Sage) on Jan 03, 2003 at 00:27 UTC
Re: Searching for and removing multi-line file entries
by jdporter (Paladin) on Jan 02, 2003 at 22:13 UTC
    Well, to read them all into an in-memory database type thing, you can do this:
    my @records; $/ = "\n["; while (<>) { chomp; my( $h, @l ) = split /\n/; $h =~ s/^\[//; $h =~ s/\]$//; push @records, { '[]' => $h, map { split /\s*=\s*/, $_, 2 } @l }; }
    Then you can find the ones that are "serverX" records, something like:   my @server_records = grep { $_->{'[]'} =~ /^server/ } @records; What else did you need to do, after this?

    jdporter
    The 6th Rule of Perl Club is -- There is no Rule #6.

Re: Searching for and removing multi-line file entries
by pg (Canon) on Jan 03, 2003 at 00:51 UTC
    This should work for you:
    use strict; my %bad = ("Zwire6" => 1, "zwire7" => 1);#this is case sensitive open(OUT, ">", "out.txt"); my @section; my $adjust = 0; my $is_server = 0; my $server_id; my $throw_away = 0; my $line; while ($line = <DATA>) { chomp($line); if ($line =~ m/^\[/) { flush_section(); $#section = -1; $throw_away = 0; push @section, $line; if ($line =~ m/^\[server(\d+)\]/) { $server_id = $1; $is_server = 1; } else { $is_server = 0; } } else { push @section, $line; if ($is_server) { if ($line =~ m/^servername\s*=\s*(.*?)\s*$/) { if (defined($bad{$1})) { $throw_away = 1; $adjust ++; } } } } } flush_section(); close(OUT); sub flush_section { if (!$throw_away) { if ($is_server) { $section[0] = "[server".($server_id - $adjust)."]"; } foreach my $line (@section) { print OUT $line, "\n"; } } } __DATA__ [reports] yearlystore = quarterlystore = monthlystore = weeklystore = dailystore = [server1] logfilepath1 = F:\Logfiles\ZWire\Zwire4\split-0\ex*.log servername = Zwire4 logfilepassword = logfileusername = id = a104136320003954 [server2] servername = Zwire6 logfilepath1 = f:\logfiles\zwire\zwire6\split-0\ex*.log id = a104136346513171 logfilepassword = logfileusername = [server3] logfilepath1 = F:\Logfiles\ZWire\Zwire4\split-0\ex*.log servername = Zwire4 logfilepassword = logfileusername = id = a104136320003954 [server4] id = a104136351461256 logfilepath1 = f:\logfiles\zwire\zwire7\split-0\ex*.log logfilepassword = servername = zwire7
      Thanks pg!

      In the time you must have been writing this, I developed a solution. I discovered that the sequential limitation was avoidable; that is: "serverX+1" did not have to follow "serverX".

      So I wrote this:
      use strict; use warnings; # use re "debug"; my $file = shift @ARGV || die "No file name given!\n"; my $server = shift @ARGV; my $output = shift @ARGV; open(IN, "$file") or die "Unable to open file\n"; undef $/; my $content = <IN>; $content =~ s/ ^\[server(\d)\] .*? servername\s=\s$server .*? \[server +\d\]$/\[server$1\]/msxi close(IN); open(OUT, ">$output"); print OUT $content; close(OUT);
      This only removes one server entry, but it will be easy enough to throw a foreach loop around it.

      Thanks again for the help!

      Cheers,
      ibanix $ echo '$0 & $0 &' > foo; chmod a+x foo; foo;
Re: Searching for and removing multi-line file entries
by JamesNC (Chaplain) on Jan 03, 2003 at 06:23 UTC
    Yet another approach... replace $test with whatever string you are looking for :) Good luck :)
    use strict; my (@BUFFER,@FINAL); my $num; my $test = "Zwire6"; my $reorder = 0; my $remove = 0; while(<DATA>){ if(/$test/ig){ $remove = 1; } # set flag to remove if found if(/\[server(\d+)\]/){ if($remove == 1){ undef @BUFFER; $remove = 0; $reorder = 1;} if($reorder == 1) { $_ =~s/server$1/server$num/;} # renumber the servers push @FINAL, @BUFFER; # transfer BUFFER to FINAL undef @BUFFER; # empty BUFFER push @BUFFER, $_; # start over :) $num = scalar $1; next; } push @BUFFER, $_; } if($remove == 1){ undef @BUFFER; $remove = 0; $reorder=1; } push @FINAL, @BUFFER; #transfer the last print @FINAL; # or send it to yer handle :) __DATA__ [reports] yearlystore = quarterlystore = monthlystore = weeklystore = dailystore = [server1] logfilepath1 = F:\Logfiles\ZWire\Zwire4\split-0\ex*.log servername = Zwire4 logfilepassword = logfileusername = id = a104136320003954 [server2] servername = Zwire6 logfilepath1 = f:\logfiles\zwire\zwire6\split-0\ex*.log id = a104136346513171 logfilepassword = logfileusername = [server3] id = a104136351461256 logfilepath1 = f:\logfiles\zwire\zwire7\split-0\ex*.log logfilepassword = servername = zwire7
    :)