Inserting a potentially missing line with perl

Viewed 125

I'm trying to modify a perl filter to insert a line that may be missing.

I might have inputs that are

A
B
C

or

A
C

A and B are fixed and known in advance. C can vary from file to file.

The real data are more complicated - callstacks generated as part of a regression test. Depending on the compiler used (and hence the optimization) there may be tail call elimination which can remove the 'B' frame. After filtering the files are simply diffed.

In the second case I want to insert the 'B' line. In the first case I don't want to insert a duplicate line. I thought that this was a job for negative lookahead, using the following

s/A.(?!B)/A\nB/s;

However this seems to mean "if any part of A.(?!B) matches the input text then substitute it with A\nB" whereas I need "if all of A.(?!B) matches" then substitute.

No matter what I try it either always substitutes or never substitutes.

1 Answers

In a one-liner for a ready test

perl -0777 -wpe's/ ^A.*\n \K (?!B.*\n) /B-line\n/xgm' file

The \K makes it drop all matches before it, so we don't have to capture and copy them back in the replacement side. With the -0777 switch the whole file is slurped into a string, available in $_.

In order to match all such A-B?-C groups of lines we need the /g modifier (match "globally"), and for the anchor ^ to also match on internal newlines we need the /m modifier ("multiline").

The /x modifier makes it disregard literal spaces (and newlines and comments), what allows for spacing things out for readability.

On the other hand, if a line starting with A must be followed by either a line starting with B, or by a line starting with C if the B-line isn't there, then it's simpler, with no need for a lookahead

perl -0777 -wpe's/ ^A.*\n \K (^C.*\n) /B-line\n$1/xgm' file

Both these work correctly in my (basic) tests.

In either case, the rest of the file is printed as is, so you can use the -i switch to change the input file "in-place," if desired, and with -i.bak you get a backup as well. So

perl -i.bak -0777 -wpe'...' file

Or you can dump the output (redirect) into the same file to overwrite it, since the whole file was first read, if this runs out of a script.


Reading the file line by line is of course far more flexible. For example

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

my $just_saw_A_line;

while (<>) { 
    if ($just_saw_A_line and not /^B/) { 
        say "B-line";
    }   

    $just_saw_A_line = /^A/;
    print
}

This also handles multiple A-(B?)-C line groups. It's far more easily adjusted for variations.

The program acts like a filter, taking either STDIN or lines from file(s) given on the command line, and printing lines to STDOUT. The output can then be redirected to a file, but not to the input file itself. (If the input file need be changed then the code need be modified for that.)

Related