在多行sed匹配

时间:2015-07-02 15:38:01

标签: regex perl replace sed

我有一个非常大的文件,其中包含以下几行:

start :234
modify 123 directory1/directory2/file.txt
delete directory3/file2.txt
modify 899 directory4/file3.txt

每个块以模式#34开始:#"并以空行结束。在块内,每一行都以"修改#"或"删除"。

我需要修改每一行的路径,特别是在前面附加一个目录。我只想使用一般的正则表达式覆盖整个文件,以便#34;修改#"或"删除",但由于该文件中的大量其他数据,可能会有其他匹配这种有些模糊的模式。所以我需要使用多行匹配来查找整个块,然后在该块中执行编辑。这可能会在一次通过中导致大约10,000次修改,因此我也尝试将执行时间缩短到不到30分钟。

我目前的尝试是sed one-liner:

sed '/^start :[0-9]\+$/ { :a /^[modify|delete] .*$/ { N; ba }; s/modify [0-9]\+ /&Appended_DIR\//g; s/delete /&Appended_DIR\//g }' file_to_edit

这是为了找到"开始"行,循环,而行以"修改"或者"删除,"然后应用sed替换。

但是,当我执行此命令时,不会进行任何更改,并且输出与原始文件相同。

我所形成的命令是否存在问题?在perl中这会更容易/更有效吗?任何帮助将不胜感激,我将澄清我能在哪里。

6 个答案:

答案 0 :(得分:0)

我认为用perl

会更好

特别是因为您可以按照记录工作'设置$/ - 如果您的记录由空行分隔,请将其设置为\n\n

这样的事情:

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

local $/ = "\n\n";
while (<>) {

    #multi-lines of text one at a time here.
    if (m/^start :\d+/) {
        s/(modify \d+)/$1 Appended_DIR\//g;
        s/(delete) /$1 Appended_DIR\//g;
    }
    print;
}

循环的每次迭代都会选出一个空行分隔的块,检查它是否以模式开头,如果它是,则应用一些变换。

它会通过管道或STDINmyscript.pl somefile获取数据。

输出到STDOUT,您可以按正常方式重定向。

以这种方式处理文件的限制因素通常是:

  • 从磁盘传输数据
  • 模式复杂性

模式越复杂,特别是如果它正在进行变量匹配,正则表达式引擎必须做的回溯越多,这可能会变得昂贵。您的转换很简单,因此打包它们并没有太大的区别,而您的限制因素可能就是磁盘IO。

(如果你想进行就地编辑,可以使用这种方法)

如果 - 如上所述 - 你不能依赖记录分隔符,那么你可以使用的是perl s range operator(其他答案已经这样做,我&#39; m只是稍微扩展一下:

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

while (<>) {

    if ( /^start :/ .. /^$/)
        s/(modify \d+)/$1 Appended_DIR\//g;
        s/(delete) /$1 Appended_DIR\//g;
    }
    print;
}

我们不再更改$/,因此它仍然默认为“每行”。我们添加的是一个范围运算符,它测试&#34;我目前是否在这两个正则表达式中#34;当你点击&#34;开始&#34;时切换truefalse当你点击一个空白行(假设你想要停止的地方?)。

如果此条件为真,则应用模式转换,如果不是,则忽略并进行打印。

答案 1 :(得分:0)

我还建议使用perl。虽然我会尝试将其保持为单线形式:

perl -i -pe 'if ( /^start :/ .. /^$/){s/(modify [0-9]+ )/$1Append_DIR\//;s/(delete )/$1Append_DIR\//; }' file_to_edit

或者你可以使用stdout的重定向:

perl -pe 'if ( /^start :/ .. /^$/){s/(modify [0-9]+ )/$1Append_DIR\//;s/(delete )/$1Append_DIR\//; }' file_to_edit > new_file

答案 2 :(得分:0)

使用gnu sed(使用BRE语法):

sed '/^start :[0-9][0-9]*$/{:a;n;/./{s/^\(modify [0-9][0-9]* \|delete \)/\1NewDir\//;ba}}' file.txt

这里的方法不是存储整个块并继续进行替换。在这里,当找到块的开始时,下一行被加载到模式空间中,如果该行不为空,则执行替换并加载下一行等,直到块结束。

注意:gnu sed具有可用的替换功能|,对于其他一些sed版本可能不是这种情况。

使用awk的方式:

awk '/^start :[0-9]+$/,/^$/{if ($1=="modify"){$3="newdirMod/"$3;} else if ($1=="delete"){$2="newdirDel/"$2};}{print}' file.txt

答案 3 :(得分:0)

sed的模式范围是你的朋友:

"Condition": {
    "StringLike": {
        "aws:Referer": [
            "http://my_bucket.s3.amazonaws.com/*",
            "https://my_bucket.s3.amazonaws.com/*",
            "http://www.example.com/*",
            "https://www.example.com/*",
        ]
    }
}

这个技巧的核心是sed -r '/^start :[0-9]+$/,/^$/ s/^(delete |modify [0-9]+ )/&prepended_dir\//' filename ,它被读作一个条件,在该条件下执行它后面的/^start :[0-9]+$/,/^$/命令。如果sed当前发现自己位于第一行匹配开始模式s并且最后一个匹配结束模式^start:[0-9]+$(空行)的行中,则条件为真。 ^$用于扩展正则表达式语法(旧{BSD seds的-r),这使得正则表达式更易于编写。

答案 4 :(得分:0)

这在Perl中非常简单,并且可能比sed等价物快得多。

此单行程序在行开头出现Appended_DIR/modify 999后插入delete。它使用范围运算符将这些更改限制为以start :999开头并以不包含可打印字符的行结束的文本块

perl -pe"s<^(?:modify\s+\d+|delete)\s+\K><Appended_DIR/> if /^start\s+:\d+$/ .. not /\S/" file_to_edit

答案 5 :(得分:0)

好悲伤。 sed用于单个行上的简单替换,即全部。一旦开始使用s,g和p以外的构造(使用-n),您使用的是错误的工具。只需使用awk:

awk '
    /^start :[0-9]+$/ { inBlock=1 }
    inBlock { sub(/^(modify [0-9]+|delete) /,"&Appended_DIR/") }
    /^$/ { inBlock=0 }
    { print }
' file
start :234
modify 123 Appended_DIR/directory1/directory2/file.txt
delete Appended_DIR/directory3/file2.txt
modify 899 Appended_DIR/directory4/file3.txt

你可以通过各种方式在awk中完成上述操作,但为了清晰起见,我用上面的方式编写了它,因为我认为你不熟悉awk但是应该没有麻烦,因为它重用您自己的sed脚本regexp和替换文本。