在 shell 脚本中对多行模式使用正则表达式

Using regex for multiple line pattern in shell script

提问人:Aram Papazian 提问时间:10/24/2023 最后编辑:Aram Papazian 更新时间:11/1/2023 访问量:137

问:

我看到了以下stackoverflow如何在shell脚本中将正则表达式用于多行模式,但它并没有完全按照我的意愿。我正在寻找一种基于终端的方式来执行就地(或)正则表达式,它将为我自动更改一些文件。(我可能可以使用xml库/等来做到这一点,但我更喜欢使用终端)。sedperl

我拥有的文件

Some text
<div class="firstClass secondClass" something="else">
    Some random stuff
</div>
Random Text
<div class="thirdClass fifthClass" something="else">
    Some random stuff
    < is something
    < but not /> This
</div>
<div class="fourthClass">
    Some random stuff
</div>
Final Text

我试着做一个足够武断的例子来展示几个不同的用例。 我正在尝试将其转换为如下所示的内容:

Some text
<!-- firstClass start -->
    Some random stuff
<!-- firstClass end -->
Random Text
<!-- thirdClass start -->
    Some random stuff
    < is something
    < but not /> This
<!-- thirdClass end -->
<!-- fourthClass start -->
    Some random stuff
<!-- fourthClass end -->
Final Text

我正在尝试以下代码:

sed -n '/<div class="\([^ "]*\)[^>]*>/,/<\/div>/{s/<div class="\([^ "]*\)[^>]*>/<!-- \1 start -->/;/<\/div>/d;p}' file

但是由于在之前的 stackoverflow 问题中,该人不想要最后一行,因此答案将其删除,这不是我想要的。可以看出,我希望在内部内容之前和之后重复第一段文字。

上面的正则表达式正确地修复了第一行(将 div 更改为注释),但我似乎无法在文本下方复制它。我试图弄乱正则表达式,但我似乎无法让它工作。它还删掉了第一行和最后一行,尽管我想保留它们。有什么想法可以做这样的事情吗?

(PS,是的,我知道我们需要一个就地命令,但出于显而易见的原因,我想在实际使用它之前对其进行测试)sed -i

编辑:关于我正在尝试做的事情的想法的一点补充。虽然上面是 HTML,但这段代码不一定是专门用于 HTML 的(因此我不需要 HTML/XML 处理)。这个想法是:

Some random text before my pattern
PATTERN "info ...
  random stuffs
END PATTERN
Some random stuff after pattern

我希望将其转换为

Some random text before my pattern
NEW PATTERN - info 
  random stuffs
END NEW PATTERN - info
Some random stuff after pattern

所以不一定有html。只是在一些文本上方采用模式,在下面复制它。唯一的条件是不会有文本,所以这就是我想基于它的基础。 将 100% 永远不会有文本。不涉及嵌套,也没有任何边缘情况。它始终与上面所示的模式相同。唯一的“边缘”情况是第一行可能有一些额外的文本,直到我不关心的换行符。该内容始终可以删除。我只关心这个词(也就是直到第一个空格字符或第一个字符。random stuffsEND PATTERNrandom stuffsEND PATTERNPATTERN "info ...info"

正则表达式 Perl SED

评论

1赞 zdim 10/24/2023
这在 HTML 处理中很深,当然,你需要一个库。例如,Mojo::D OM 很棒,而我也很好地使用了 HTML::TreeBuilder。然后,您需要尽可能准确地表达您的要求。(始终/仅元素?有嵌套吗?...)div
0赞 Aram Papazian 10/24/2023
没有嵌套,总是div。问题是它只是“html”处理,因为我给出的示例使用了而不是或其他字符串。这就是为什么我想远离 HTML 处理的原因。我也想将它与html以外的东西一起使用。我将尝试添加一个附录,其中包含有关该想法的更多信息。<div>##some
0赞 zdim 10/24/2023
"。它只是“html”处理,因为我给出的示例使用 <div>而不是 ##some 或其他字符串。--哼?但是您想在 和 标签之间捕获?随便怎么称呼它,但那是结构化文本。“##some”怎么样?最后的要素是什么?最好是一些已知的格式,您可以在其中使用库,否则您必须编写一个解析器,恐怕它不会是单行的。(除非你真的有一个微不足道的案例<div></div>start--text--stop)
0赞 zdim 10/24/2023
"添加一个附录,其中包含更多关于这个想法的信息“——无论如何,因为你现在发布的内容非常清楚:使用 HTML 解析器(如果您想要答案,请更好地指定问题)。但是,我再次建议尽量具体一点。你在评论中提到的是非常开放的
0赞 zdim 10/24/2023
如果您确定它非常简单(没有嵌套、已知的启停标签、没有边缘情况等),请清楚地说明这一点。在这种情况下,是的,你可以有一个简单的正则表达式,如果这真的是它的全部内容

答:

0赞 tripleee 10/24/2023 #1

这是一个简单的 Awk 脚本,它提取之后的第一个令牌,并在替换文本中使用该令牌。class="

awk '/<div class="/ { sub(/.*<div class="/, ""); sub(/[" ].*/, "");
    class=$0; print "<--", class, "start -->"; next }
  /<\/div>/ { print "<--", class", "end -->"; class=""; next }
  1' file >new

就正则表达式匹配而言,这里没有“多行”,只是一个简单的工具,用于记住行之间的某些状态。Awk 仍然一次检查一行(尽管如果需要,更改它也不难;RS

1赞 potong 10/24/2023 #2

这可能对你有用 (GNU sed):

sed -E '/^<div class="([^ "]*).*/{
          s//<!-- \1 start -->/;h;:a;n;/^<\/div>$/!ba;g;s/\bstart/end/}' file

匹配起始 div。

将该行操作为所需的格式并制作副本。

打印/获取下一行,直到结束 div。

将该行替换为副本,并替换为并打印结果。startend

重复。

2赞 zdim 10/25/2023 #3

对于初学者来说,这里有一个简单的方法,适用于我对特定发布文本的测试

s{<div\s+ class="(\S+) (.*?) </div>}{<!-- $1 --> $2 <!-- $1 end -->}sxg;

修饰符是:这样也匹配换行符(通常不匹配),这样文字空格就会被忽略,这有助于可读性,并且这样才能继续通过字符串,匹配和替换。s.xg

为此,我建议在文件中使用一个程序,而不是命令行程序(“单行”),但由于这里的问题中特别要求这样做

perl -0777 -wpe'
    s{<div\s+ class="(\S+) (.*?) </div>}{<!-- $1 --> $2 <!-- $1 end -->}sxg'

该开关使它把整个文件读入变量中,这是 Perl 中许多东西的默认值——在这种情况下是正则表达式的运算符。请参阅 perlrun 中的开关-0777$_s{}{}


在一个更大、更结构化的程序中,你也许可以在变量中拥有开始和结束模式,因为

s{$pbeg (.*?) $pend}{...}sxg

在这种情况下,它会在哪里

my $pbeg = qr{<div\s+ class="(\S+)};
my $pend = qr{</div>}

但是,如果这些模式变得复杂,这可能会变得笨拙/

1赞 Ed Morton 10/25/2023 #4

在第一个示例中,将 GNU awk 用于第三个参数以匹配 ()强类型的正则表达式常量

$ cat defs1.awk
BEGIN {
    begReg = @/<div\s+class="([^" ]+)/
    endReg = @/<\/div>/
    begFmt = "<!-- %s start -->"
    endFmt = "<!-- %s end -->"
}

$ cat common.awk
match($0,begReg,a) {
    key = a[1]
    $0 = sprintf(begFmt,key)
}
match($0,endReg,a) {
    $0 = sprintf(endFmt,key)
}
{ print }

$ awk -f defs1.awk -f common.awk file1
Some text
<!-- firstClass start -->
    Some random stuff
<!-- firstClass end -->
Random Text
<!-- thirdClass start -->
    Some random stuff
    < is something
    < but not /> This
<!-- thirdClass end -->
<!-- fourthClass start -->
    Some random stuff
<!-- fourthClass end -->
Final Text

对于您的第二个示例,我们只需要一个新的定义文件,但可以从上面重用:common.awk

$ cat defs2.awk
BEGIN {
    begReg = @/PATTERN "([^" ]+)/
    endReg = @/END PATTERN/
    begFmt = "NEW PATTERN - %s"
    endFmt = "END NEW PATTERN - %s"
}

$ awk -f defs2.awk -f common.awk file2
Some random text before my pattern
NEW PATTERN - info
  random stuffs
END NEW PATTERN - info
Some random stuff after pattern

请注意,我们只是在 2 个文件的部分中定义所需的输入正则表达式和输出格式,我们不会更改 .所依赖的只是,您可以在正则表达式中定义与开始分隔符匹配的第一个捕获组,以包含要在开始行和结束行中保留/打印的关键信息。BEGINdefs*.awkcommon.awk

你并不严格需要 endReg 匹配,但我使用它,以防你将来需要为其他结束分隔符格式调整它。match()

只需更改为执行与所有其他工具相同的 pseduo 就地编辑即可。awkawk -i inplace