Print lines followed by a line matching a pattern, to file A, if no match then print to file B

Viewed 82

I am trying to write a Perl script to parse a file and segregate its contents based on a match criteria. A sample file (for e.g. test.txt) would look like something:

command something something keyword something something
filepath1
filepath2
command something something something something
filepath3
filepath4
filepath5

The output of the script would be two files based on the name of input file for the script, test.keyword and test.nomatch.

test.keyword would have something like this:

command something something keyword something something
filepath1
filepath2

test.nomatch would have something like this:

command something something something something
filepath3
filepath4
filepath5

I have tried searching for ways of achieving this but I am unable to find something that helps me. This is the only part of my script which is left and has me going crazy now. And yes, I am not an expert in Perl. :(

Following is the skeleton that I have at the moment which is waiting for that golden loop condition:

#!/usr/bin/perl -a

my $fh = shift or die "No file passed as argument for splitting!\n";
open( infile, "<$fh" ) or die $!;
open( vef_output, ">$fh.vef" ) or die $!;
open( svf_output, ">$fh.svf" ) or die $!;

$mode = 0; #0 for vef and 1 for svf

while ( my $line = <$infile> ) {
        if ( $line =~ /SOME_LIBRARY_OPTIONS/ ) {
        if ( $line =~ /\-sv/ ) {
            print {$svf_output} $line;
            $mode = 1;
        }
        else {
            print {$vef_output} $line;
            $mode = 0;
        }
        next;
    }
    if ( $mode eq 0 ) {
        print {$vef_output} $line;
        next;
    }
    else {
        print {$svf_output} $line;
        next;
    }   
}
close($vef_output);
close($svf_output);
close($file);
2 Answers

While the logic of your code is correct and you surely will find some remaining typos on your own, I would like to suggest a modification to your while loop:
Each line of your input file will have to be printed once (except perhaps for the beginning of the input file). Instead of setting a $mode flag and testing that, I'd rather switch the output filehandle , which leads to clearer code:

#!/usr/bin/perl
use strict;
use warnings;

my $filename = shift or die "No file passed as argument for splitting!\n";

# add filename to error message - users will like it!
open( my $infile, "<", $filename ) or die "could not open $filename: $!";
open( my $vef_output, ">","$filename.vef" )
    or die "could not open $filename.vef: $!";
open( my $svf_output, ">","$filename.svf" )
    or die "could not open $filename.svf: $!";

my $active_out;

while ( my $line = <$infile> ) {
    if ( $line =~ /SOME_LIBRARY_OPTIONS/ ) {
        $active_out = $vef_output;
    }
    # depending on your regex this conditional can be nested or not...
    if ( $line =~ /-sv/ ) {
        $active_out =  $svf_output;
    }
    next unless defined $active_out;
    print $active_out $line;
}

close($vef_output);
close($svf_output);
close($infile);

The code could be written in a simple more readable form

#!/usr/bin/env perl
#
# vim: ai ts=4 sw=4

use strict;
use warnings;

my $fh = shift
    or die "No file passed as argument for splitting!\n";

open my $in,          '<', $fh or die $!;
open my $out_match,   '>', $fh . 'vef' or die $!;
open my $out_nomatch, '>', $fh . 'svf' or die $!;

my $output_ready;
my $fh_out;

while ( <$in> ) {
        $output_ready = 1 if /SOME_LIBRARY_OPTIONS/;
        if( /-sv/ ) {
            $fh_out = $out_match;
        } else {
            $fh_out = $out_nomatch;
        }
        print $fh_out, $_ if $output_ready;
}

close $in;
close $out_match;
close $out_nomatch;
Related