Home > Net >  Search and replace multiple lines from a file
Search and replace multiple lines from a file

Time:09-17

I'm trying to remove a part of a.txt file and replace with contents of b.txt file while also doing modification to other lines in a.txt using a Perl program.

file a.txt

line1
line2 
replace from below line
replace from this line
bla bla...
bla bla...
to this line
line3
line4

file b.txt

replacement1
replacement2
replacementn 

Below is my code which is not working.

#!/apps/perl/5.8.3/bin/perl -w
   open (INPUT, "a.txt")              or die $!;
   open (REPLACE, "b.txt")            or die $!;
   open (OUTPUT, ">c.txt")          or die $!;

my $replace_text;
{
    local $/;
    $replace_text = <REPLACE>;
}
close(REPLACE);

while (<INPUT>) {
   s/line1/modified_line1/;
   s/line2/modified_line2/;
   
   if($_ =~ /replace from below line/){
       while(<INPUT>){
             {
                local undef $/;
                s/replace from this line.*to this line/$replace_text/smg;
             }
       s/line3/modified_line3/;
       s/line4/modified_line4/;
       print OUTPUT;
   }
}
}
close(INPUT);
close(OUTPUT);

Expected output file c.txt

modified_line1
modified_line2
replacement1
replacement2
replacementn
modified_line3
modified_line4

Can someone help me understand where I'm going wrong?

CodePudding user response:

I don't think you need nested while loops to read your input file.

One way is to use a variable to control when you print to the output file:

use warnings;
use strict;

open (INPUT, "a.txt")   or die $!;
open (REPLACE, "b.txt") or die $!;
open (OUTPUT, ">c.txt") or die $!;

my $replace_text;
{
    local $/;
    $replace_text = <REPLACE>;
}
close(REPLACE);

my $print = 1;
while (<INPUT>) {
    s/line(\d)/modified_line$1/;
    $print = 0 if /replace from below line/;
    if (/to this line/) {
        $print = 1;
        $_ = $replace_text;
    }
    print OUTPUT if $print;
}
close(INPUT);
close(OUTPUT);

Output:

modified_line1
modified_line2 
replacement1
replacement2
replacementn
modified_line3
modified_line4

I also consolidated your 4 line substitutions into 1 using \d.

CodePudding user response:

As much as I like perl, it's really not necessary here:

sed -e 's/line1/modified_line1/' \
    -e 's/line2/modified_line2/' \
    -e 's/line3/modified_line3/' \
    -e 's/line4/modified_line4/' \
    -e '/replace from below/rb.txt' \
    -e '/replace from below/,/to this line/d' a.txt
modified_line1
modified_line2
replacement1
replacement2
replacementn
modified_line3
modified_line4

If you did want to use perl, I'd just do:

#!/usr/bin/env perl
use strict;
use warnings;
open my $ah, '<', "a.txt" or die "a.txt: $!\n";

while(<$ah>) {
        s/line1/modified_line1/;
        s/line2/modified_line2/;
        s/line3/modified_line3/;
        s/line4/modified_line4/;
        if( /replace from below/ ){
                system "cat b.txt" and exit 1;
        }
        next if( /replace from below/ .. /to this line/);
        print;
}

CodePudding user response:

The problem description does not specify how big can be a.txt file. Posted code utilizes regular expression with modifier /smg what indicates that OP tries to work on multiline text.

Let's assume that input file is small enough to be read and processed in the memory.

For code manageability substitute placed into __DATA__ block which read in %substitute hash.

Build regular expression $re based on keys %substitute to utilize in substitution pattern.

Multiline substitution is based on original OP's code (is not applicable to line by line read of input data).

Two subroutines defined to read content of the file into variable and to store variable data into a file -- just to make the code easier to read and understand.

use strict;
use warnings;
use feature 'say';

my($fname_in,$fname_repl,$fname_out) = qw/a.txt b.txt c.txt/;

my %substitute = split(/[,\s]/, do{ local $/; <DATA>} );
my $re = '\b(' . join('|',keys %substitute) . ')\b';

my $data = read_file($fname_in);
my $replace_with = read_file($fname_repl);

$data =~ s/$re/$substitute{$1}/g;
$data =~ s/replace from below line.*?to this line/$replace_with/gsm;

save_file($fname_out,$data);
say $data;

exit 0;

sub read_file {
    my $fname = shift;
    my $data;
    
    open my $fh, '<', $fname
        or die "Couldn't open $fname";
    $data = do { local $/; <$fh> };
    close $fh;
    
    return $data;
}

sub save_file {
    my $fname = shift;
    my $data  = shift;
    
    open my $fh, '>', $fname
        or die "Couldn't open $fname";
    say $fh $data;
    close $fh;
}

__DATA__
line1,modified_line1
line2,modified_line2
line3,modified_line3
line4,modified_line4

Output

modified_line1
modified_line2
replacement1
replacement2
replacementn
modified_line3
modified_line4
  • Related