user3271367
user3271367

Reputation: 31

find a match and replace next line in perl

I am working on the perl script and need some help with it. The requirement is, I have to find a lable and once the label is found, I have to replace the word in a line immediately following the label. for Example, if the label is ABC:

ABC:
string to be replaced 
some other lines
ABC: 
string to be replaced
some other lines
ABC: 
string to be replaced

I want to write a script to match the label (ABC) and once the label is found, replace a word in the next line immediately following the label.

Here is my attempt:

open(my $fh, "<", "file1.txt") or die "cannot open file:$!";

while (my $line = <$fh>)) 
{
    next if ($line =~ /ABC/) {

    $line =~ s/original_string/replaced_string/;
  }
  else {
    $msg = "pattern not found \n ";
    print "$msg";
  }
}

Is this correct..? Any help will be greatly appreciated.

Upvotes: 3

Views: 7027

Answers (5)

Kenosis
Kenosis

Reputation: 6204

The following one-liner will do what you need:

perl -pe '++$x and next if /ABC:/; $x-- and s/old/new/ if $x' inFile > outFile

The code sets a flag and gets the next line if the label is found. If the flag is set, it's unset and the substitution is executed.

Hope this helps!

Upvotes: 2

Miller
Miller

Reputation: 35198

Also, relying on perl's in-place editing:

use File::Slurp qw(read_file write_file);

use strict;
use warnings;

my $file = 'fakefile1.txt';

# Initialize Fake data
write_file($file, <DATA>);

# Enclosed is the actual code that you're looking for.
# Everything else is just for testing:
{
    local @ARGV = $file;
    local $^I = '.bac';

    while (<>) {
        print;
        if (/ABC/ && !eof) {
            $_ = <>;
            s/.*/replaced string/;
            print;
        }
    }

    unlink "$file$^I";
}

# Compare new file.
print read_file($file);

1;

__DATA__
ABC:
string to be replaced 
some other lines
ABC: 
string to be replaced
some other lines
ABC: 
string to be replaced
ABC: 

outputs

ABC:
replaced string
some other lines
ABC:
replaced string
some other lines
ABC:
replaced string
ABC:

Upvotes: 0

David W.
David W.

Reputation: 107040

You're doing this in your loop:

 next if ($line =~ /ABC/);

So, you're reading the file, if a line contains ABC anywhere in that line, you skip the line. However, for every other line, you do the replacement. In the end, you're replacing the string on all other lines and printing that out, and your not printing out your labels.

Here's what you said:

  • I have to read the file until I find a line with the label:
  • Once the label is found
  • I have to read the next line and replace the word in a line immediately following the label.

So:

  • You want to read through a file line-by-line.
    • If a line matches the label
      • read the next line
      • replace the text on the line
    • Print out the line

Following these directions:

use strict;
use warnings;                   # Hope you're using strict and warnings
use autodie;                    # Program automatically dies on failed opens. No need to check
use feature qw(say);            # Allows you to use say instead of print


open my $fh, "<", "file1.txt";  # Removed parentheses. It's the latest style

while (my $line = <$fh>) {
    chomp $line;               # Always do a chomp after a read.
    if ( $line eq "ABC:" ) {   # Use 'eq' to ensure an exact match for your label
        say "$line";           # Print out the current line
        $line = <$fh>          # Read the next line
        $line =~ s/old/new/;   # Replace that word
   }
   say "$line";                # Print the line
}
close $fh;                     # Might as well do it right

Note that when I use say, I don't have to put the \n on the end of the line. Also, by doing my chomp after my read, I can easily match the label without worrying about the \n on the end.

This is done exactly as you said it should be done, but there are a couple of issues. The first is that when we do $line = <$fh>, there's no guarantee we are really reading a line. What if the file ends right there?

Also, it's bad practice to read a file in multiple places. It makes it harder to maintain the program. To get around this issue, we'll use a flag variable. This allows us to know if the line before was a tag or not:

use strict;
use warnings;                   # Hope you're using strict and warnings
use autodie;                    # Program automatically dies on failed opens. No need to check
use feature qw(say);            # Allows you to use say instead of print

open my $fh, "<", "file1.txt";  # Removed parentheses. It's the latest style

my $tag_found = 0;             # Flag isn't set
while (my $line = <$fh>) {
    chomp $line;               # Always do a chomp after a read.
    if ( $line eq "ABC:" ) {   # Use 'eq' to ensure an exact match for your label
        $tag_found = 1         # We found the tag!
    }
    if ( $tag_found ) {
        $line =~ s/old/new/;   # Replace that word
        $tag_found = 0;        # Reset our flag variable
    }
    say "$line";               # Print the line
}
close $fh;                     # Might as well do it right

Of course, I would prefer to eliminate mysterious values. For example, the tag should be a variable or constant. Same with the string you're searching for and the string you're replacing.

You mentioned this was a word, so your regular expression replacement should probably look like this:

$line =~ s/\b$old_word\b/$new_word/;

The \b mark word boundaries. This way, if you're suppose to replace the word cat with dog, you don't get tripped up on a line that says:

The Jeopardy category is "Say what".

You don't want to change category to dogegory.

Upvotes: 1

albe
albe

Reputation: 551

Or you could use File::Slurp and read the whole file into one string:

use File::Slurp;
$x = read_file( "file.txt" );
$x =~ s/^(ABC:\s*$ [\n\r]{1,2}^.*?)to\sbe/$1to was/mgx;         
print $x;

using /m to make the ^ and $ match embedded begin/end of lines
x is to allow the space after the $ - there is probably a better way
Yields:

ABC:
string to was replaced 
some other lines
ABC: 
string to was replaced
some other lines
ABC: 
string to was replaced

Upvotes: 0

Gabs00
Gabs00

Reputation: 1877

Your problem is that reading in a file does not work like that. You're doing it line by line, so when your regex tests true, the line you want to change isn't there yet. You can try adding a boolean variable to check if the last line was a label.

#!/usr/bin/perl;

use strict;
use warnings;

my $found;
my $replacement = "Hello";
while(my $line = <>){
    if($line =~ /ABC/){
        $found = 1;
        next;
    }
    if($found){
        $line =~ s/^.*?$/$replacement/;
        $found = 0;
        print $line, "\n";
    }
}

Upvotes: 0

Related Questions