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

The example input tcl file content:

# Bind ::d::autoBindmap $intfcName -------------so many other lines------------------------------ ############ Power information for vss ##################### #---------------------------- #pg_vss s100 #---------------------------- #Add pg_vss_dl interface set intfcName pg_vss 1.0 s 100 #Add Verilog port binding for vss ::d::autoBindmap $module pg_vss $intfcName { VSSGND vss_dl; } ############ Power information for vdd ##################### #---------------------------- #pg_vdd s100 #---------------------------- #Add pg_vdd_dl interface set intfcName pg_vdd 1.0 s 100 #Add Verilog port binding for vss ::d::autoBindmap $module pg_vdd $intfcName { VDD vss_dl; } -----------------------so many other lines---------------------------- +--- #write component ::d::writeLib $module

Purpose:

I have a array (which is generated from other part of code- not presented here) consisting of strings. The array content is changing for different files. If any of the array element is found in a particular line of file using pattern matching , I need to remove 11 lines after that and the line itself from a file.

Problem:

This change should be made in current file itself. So, I tried opening the file in read-write mode. But, I am seeing garbage being printed.

For example (see input tcl file above) :

I need to remove all the lines (in this case 11) till "}" after "Power information for $rpow " (for example $rpow is vss) is found, with the line itself being removed -where vss is an element of my remove_power_list array. This removal should happen for all the array elements.

Please if anybody can solve this issue?

Only problematic code being presented here:

my $rpow; for $rpow (@remove_power_list) { my $bf_content; open $bf_content, '+<', $bf_bind; my $count = 0; while (<$bf_content>){ if($_ =~ /Power\s+information\s+for\s+$rpow\s+/) { $count = $. + 11; } else { if($count != 0 && $. < $count) { } elsif($count != 0 && $. == $count) { $count =0; } else { print $bf_content $_; } } } close $bf_content; }

Note: My perl packages does not support File:Slurp and other special Array packages.

Expected example output:

# Bind ::d::autoBindmap $intfcName -------------so many other lines------------------------------ ############ Power information for vdd ##################### #---------------------------- #pg_vdd s100 #---------------------------- #Add pg_vdd_dl interface set intfcName pg_vdd 1.0 s 100 #Add Verilog port binding for vss ::d::autoBindmap $module pg_vdd $intfcName { VDD vss_dl; } -----------------------so many other lines---------------------------- +---

Replies are listed 'Best First'.
Re: Remove multiple lines in file if pattern found based on array element, in perl
by wrog (Friar) on Nov 06, 2014 at 23:59 UTC
    1. Learn to indent; your code is almost completely unreadable.

    2. There is only one file pointer that keeps track of where you are in the file, so by the time you've read over something and decided whether you want to keep it or not, you've already passed it and you'd need to use seek to move the pointer back to the start of it (if, say, you wanted to overwrite it because you're not keeping it), except that

    3. You're using buffered IO (<> and print) rather than sysread and syswrite, which means that what you've read may have nothing to do with where the file pointer actually is. When reading, the file pointer will keep moving forward to fill up the buffer and <> returns what's in the buffer to you one line at a time; when you switch to writing, assuming you're not using one of the modes that moves the pointer just because you switched (e.g., +>>), the pointer could be anywhere up to a buffer's worth further forward than you think it is -- and since you don't know how large the buffer is, that means basically anywhere -- and thus you're just randomly scrambling things when you write.

    4. There is really no way to edit a text file in place. In particular, there is no way to delete stuff from the middle of a file in place; you'd have to copy over everything that comes afterwards to fill in the space, at which point you may as well be rewriting the file anyway. The only cases where this actually works well is if you have a file consisting entirely of fixed-length records where you can just "black out" a particular stretch (i.e., there's something in your format that can say a record has been deleted, or maybe copy in a record from the end of the file, thus shortening it, if the order of the records doesn't matter), but then this won't be a text file anymore and also, you'd have to use seek and syswrite to do this properly.

    5. The code you've written is already pretty much as if you're copying the file anyway; it looks to me like if you change all of your print statements so that you're writing to a different file, then unlink the original file, and rename the new one, chances are it'll do what you want.
Re: Remove multiple lines in file if pattern found based on array element, in perl
by Anonymous Monk on Nov 06, 2014 at 23:39 UTC

    Understand your data. Something like this works.

    use warnings; use strict; while(<DATA>){ print unless/^#+?\s.+vss #+?$/sm .. /\}/; } __DATA__ # Bind ::d::autoBindmap $intfcName -------------so many other lines------------------------------ ############ Power information for vss ##################### #---------------------------- #pg_vss s100 #---------------------------- #Add pg_vss_dl interface set intfcName pg_vss 1.0 s 100 #Add Verilog port binding for vss ::d::autoBindmap $module pg_vss $intfcName { VSSGND vss_dl; } ############ Power information for vdd ##################### #---------------------------- #pg_vdd s100 #---------------------------- #Add pg_vdd_dl interface set intfcName pg_vdd 1.0 s 100 #Add Verilog port binding for vss ::d::autoBindmap $module pg_vdd $intfcName { VDD vss_dl; } -----------------------so many other lines---------------------------- +--- #write component ::d::writeLib $module
    You know one of the value you are looking for will be "vss", but since I know what your array holds. This could do.

      Thank you for reply. The problem here is that @remove_power_list keeps on changing, for each file. @remove_power_list is generated on the fly in bigger part of code. It may or may not have "vss" as one of its element.

      So, I cannot use any fixed string here. I have to iterate through @remove_power_list array.

      Hope this makes sense.

Re: Remove multiple lines in file if pattern found based on array element, in perl
by RichardK (Parson) on Nov 07, 2014 at 11:18 UTC

    Re-opening the same file over and over isn't very efficient, so why not read the file into memory and work on it there?. splice lets you remove a chunk of lines from an array, and <> lets you read a file into an array.

    So, something along these lines should work.

    use v5.18; use warnings; use autodie; my @replace = (qw/vss vdd/); open(my $in,'<','input.txt'); my @lines = <$in>; close $in; LOOP: for my $word (@replace) { my $start = -1; for my $i (0..$#lines) { if ($start == -1) { next unless $lines[$i] =~ /Power\s+information\s+for\s+$word\s+/ +; $start = $i; } else { next unless $lines[$i] =~ /}/; splice @lines,$start,$i - $start + 1; redo LOOP; } } } say @lines; # save @lines to temporary file name # rename input.txt to backup.txt # rename temporary file to input.txt
Re: Remove multiple lines in file if pattern found based on array element, in perl
by BillKSmith (Monsignor) on Nov 07, 2014 at 13:43 UTC

    Perl's /i runtime flag does not apply exactly to your situation, but it is a good idea to imitate its file renaming etc.

    You can test for all the elements of the array at once.

    my $rprow = join '|', @remove_power_list; $rprow = qr/(?:$rprow)/;

    Use $rprow as before, but the loop on @remove_power_list is no longer needed.

    Bill

      Thank you Bill for answer. It works, but was not able to remove the empty comments along with that.

      #######Remove power using remove_power_list ########### my $outfile; my $arr_content; my $rprow = join '|', @remove_power_list; $rprow = qr/(?:$rprow)/; if (@remove_power_list != 0) { my $bf_content; open $bf_content, '<', $bf_bind; ###bf_bind is file in bigger +for loop my @bf_array = <$bf_content>; close $bf_content; open $outfile, '>', $bf_bind; for $arr_content (@bf_array){ next if (@arr_content=~/$rprow/); print $outfile $arr_content; } close $outfile; }

      I think if there is any other string (not in my intended lines) matching with the element (for example "vcchg") from @remove_power_list array, should it remove those lines too. I tested some cases by putting string like: "set intfcInstName vcchg", outside or inside my unintended block and it doesn't delete, which is good. But, I don't know why.