【问题标题】:Finding/replacing a regex pattern within a string between two marker strings在两个标记字符串之间的字符串中查找/替换正则表达式模式
【发布时间】:2017-12-20 00:35:20
【问题描述】:

与其说是正则表达式 模式,不如说是 如何 来实现它。我已经尝试过 perl、sed 和 awk(每种都进行了多次尝试),但我不确定这作为单行代码的可能性有多大(我宁愿不编写 perl 脚本)。

说我有

#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)

我想做的是用 SQL NOW() 替换所有这些字符串日期。具体来说,对于 Perl,我尝试了以下方法:

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

但它完全删除了所有感兴趣的块(#MARKER_TOP 等),并用 NOW() 替换它,这对于我想要的来说过于严厉了。

【问题讨论】:

  • “并非所有 INSERT 表都会被标记”是什么意思?
  • 我的意思是其中一些 SQL 语句(即INSERT INTO...)将没有标记(MARKER_TOP, MARKER_BOTTOM),这个文件长达数千行,包含多个INSERT 语句。那些没有标记的,不应该改变日期。

标签: regex perl awk sed command-line


【解决方案1】:

一个简单的方法是使用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;
}

或者单排

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

我用"..." 分隔代码以便能够在内部使用'(还有其他方法)。

我假设在'' 内,一行中有一个日期,格式严格如问题中给出。我通过添加另一个 INSERT 部分进行了测试,但没有标记。替换只发生在带标记的括号中。


范围运算符通过保持状态来工作:一旦它的左操作数变为真,它就变为真,并且它保持真直到右操作数变为真,之后它在下一次迭代中返回假。它在标量上下文中以这种方式工作,而在列表上下文中它返回该范围内的列表。请参阅链接的文档。

【讨论】:

    【解决方案2】:

    你可以像这样使用awk

    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
    

    这是它的工作原理:

    • /#MARKER_TOP/{m=1} :当我们得到文本 #MARKER_TOP 时设置一个标志 m=1
    • /#MARKER_BOTTOM/{m=0} :当我们收到文本 #MARKER_BOTTOM 时重置标志 m=0
    • m{gsub(/.../, "NOW()")} : 当m==1 然后使用正则表达式将日期字符串替换为NOW()
    • 1 : 打印每条记录

    【讨论】:

    • 很好,但我认为封闭的' 也应该被删除。另外,Mawk 不支持{n}
    • 好的,谢谢,更正了单引号问题。希望 OP 不使用 mawk 否则正则表达式也必须更改。
    • @anubhava 效果很好。你介意解释一下吗(简要地)?我对awk 并不那么熟悉。谢谢!
    • 当然,我已经在答案中添加了解释。 \047 是八进制的单引号。
    • 如果你有 gnu awk 然后使用:awk -i inplace '/#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 否则使用老式的:awk '...' file &gt; _tmp.out &amp;&amp; mv _tmp.out file
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-26
    • 2019-05-19
    • 2012-05-19
    • 1970-01-01
    • 2012-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多