简体   繁体   中英

Finding/replacing a regex pattern within a string between two marker strings

It's not so much the regex pattern as it is how to achieve it. I've attempted perl, sed, and awk (various attempts with each), but I'm not sure how possible this is as a one-liner (I'd prefer to not write a perl script).

Say I have

#MARKER_TOP
INSERT INTO ('col1', 'col2', 'col3')
VALUES
(123,123,'2018-20-20 24:24:24',123)
...etc.
(123,123,'2018-20-20 24:24:24',123);
#MARKER_BOTTOM

...and more! (not all INSERT tables will be marked, btw)

What I'd like to do is replace all those string dates with SQLs NOW() . Specifically, with Perl, I've tried the following:

perl -w -pi.bak -e "undef $/; s/(#MARKER_TOP.*)'[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]'(.*#MARKER_BOTTOM)/$1 NOW() $2/msg" test.sql

But it's ripping all of the blocks of interest ( #MARKER_TOP , etc.) out completely and replacing it with NOW() which is way too heavy-handed for what I'm wanting.

A simple way is to use the range operator

use warnings;
use strict;

my $file = 'test.sql';
open my $fh, '<', $file or die "Can't open $file: $!";

my $re_date = qr/'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'/;

while (<$fh>) 
{
    if (/#MARKER_TOP/ .. /#MARKER_BOTTOM/) {
        s/$re_date/NOW()/;
    }   

    print;
}

or in a one-liner

perl -wpe"s/'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'/NOW()/ 
    if /#MARKER_TOP/ .. /#MARKER_BOTTOM/
" test.sql

where I delimit the code with "..." so to be able to use ' inside (there are other ways).

I assume that there is a single date on a line, in the format strictly as given in the question, inside '' . I tested by adding another INSERT section but without markers. The replacements happen only in the markers-bracketed one.


The range operator works by keeping the state: once its left operand turns true it becomes true, and it stays true until the right operand turns true, after which it returns false in the next iteration. It works this way in scalar context, while in list context it returns a list within that range. See linked docs.

You can use awk like this:

awk '/#MARKER_TOP/{m=1} /#MARKER_BOTTOM/{m=0} m{
   gsub(/\047[0-9]{4}(-[0-9]{2}){2} [0-9]{2}(:[0-9]{2}){2}\047/, "NOW()")} 1' file

#MARKER_TOP
INSERT INTO ('col1', 'col2', 'col3')
VALUES
(123,123,NOW(),123)
...etc.
(123,123,NOW(),123);
#MARKER_BOTTOM

Here is how it works:

  • /#MARKER_TOP/{m=1} : set a flag m=1 when we get text #MARKER_TOP
  • /#MARKER_BOTTOM/{m=0} : reset flag m=0 when we get text #MARKER_BOTTOM
  • m{gsub(/.../, "NOW()")} : when m==1 then replace date string with NOW() using regex
  • 1 : print each record

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM