sed regex 无法捕获包含该模式的整个段落

use*_*898 3 xml sed text-processing regular-expression

我有这个 XML 文件(示例)

<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>


<This is a line of text with a year=33020 month=12 in it
This line of text does not have a year or month in it
This year=33020 is the current year the current month=1
This is the year=33020 the month=2/>
Run Code Online (Sandbox Code Playgroud)

使用sed我的 Linux 发行版 (sed (GNU sed) 4.2.2) 提供的安装,我在这个文件中使用以下正则表达式进行搜索:

 sed -En 'N;s/\<(This.*2020.*[\s\S\n]*?)\>/\1/gp' test2.txt
Run Code Online (Sandbox Code Playgroud)

但是,它仅捕获此字符串:

<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
Run Code Online (Sandbox Code Playgroud)

但是我尝试捕获包含模式的和之间的整个第一段<>

我在这里做错了什么?

ter*_*don 7

当你想到这个不工作的原因是,<>没有需要在正则表达式来进行转义做,他们没有任何特殊含义。然而,\<\> 有GNU扩展正则表达式(你与激活特殊的含义-E):他们在单词边界匹配。\<匹配单词的开头和\>结尾。所以\<(This实际上不是匹配<,而是匹配单词的开头This。最后的也类似\>。GNUsed手册有一个例子,这几乎正是你所追求的:

$ sed -En '/./{H;1h;$!d} ; x; s/(<This.*2020.*?>)/\1/p;' file
<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>
Run Code Online (Sandbox Code Playgroud)

我觉得sed特别不适合这种任务。我会用perl

$ perl -000 -ne 'chomp;/<.*2020.*?>/s && print "$_\n"; exit' file
<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>
Run Code Online (Sandbox Code Playgroud)

在这里,我们在“段落模式” ( -000)中使用 Perl,这意味着“行”由两个连续\n字符定义,由一个空行定义。该脚本将:

  • chomp:删除“行”(段落)末尾的尾随换行符。
  • /<.*2020.*?>/s && print "$_\n":如果此“行”(段落)匹配 a <then 0 或多个字符直到2020和零个或多个字符然后 a >,则打印此行并附加换行符 ( print "$_\n")。s匹配运算符的修饰符允许.匹配换行符。

另一种选择是awk

$ awk 'BEGIN{RS="\n\n"} /<.*2020.+?>/' file
<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>
Run Code Online (Sandbox Code Playgroud)

我们将记录分隔符设置RS为两个连续的换行符,然后使用与上面相同的正则表达式进行匹配。由于在awk找到匹配项(或任何其他操作返回 true)时的默认行为是打印当前记录,这将打印出您需要的内容。

  • @ user63898 是的,当然会。你为什么要把它和`-i`一起使用?您的问题是显示一个包含两个“段落”的示例文件,而您说您只想要第一个。请记住,我们无法神奇地猜测您想要什么:您需要告诉我们。 (2认同)

Adm*_*Bee 6

首先,大多数文本处理工具,例如sedawk逐行工作,因此匹配整个段落需要一点额外的努力。这是可能的,但这也是您看到的意外输出的原因之一。

其次,由于 XML 标记分隔字符,您的输入看起来像结构化文本。因此,最好使用xmlstarlet或其他专用工具对其进行处理。(更新:既然您现在在评论中确认了这一点,我强烈建议您使用xmlstarlet或类似的工具。)

也就是说,如果您的文本看起来像您的示例,并且您的awk安装接受多字符记录分隔符(如 GNU Awk),则以下程序应该可以工作:

awk -v RS="<|/>" '/2020/' input.txt
Run Code Online (Sandbox Code Playgroud)

RS变量,如果由多于一个字符的,将被解释为正则表达式,所以不管是</>将“记录分隔符”来处理,而不是默认的\n。因此,任何匹配条件都将应用于这些标签之间的整个文本,而不仅仅是单个行。

结果:

This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2
Run Code Online (Sandbox Code Playgroud)

请注意,“tag-open”<和“tag-close”/>字符组合从输出中删除,因为它们被选为记录分隔符。另一方面,这意味着如果“段落”没有被空行分隔,它也将起作用。(但是,如果在这些标签之外有与您的模式匹配的“杂散”文本,它也会被匹配。

You would place the regular expression you are looking for inside the / ... / part of the program (just as in a sed adress statement). If you are looking for a fixed string, however, I would recommend

This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2
Run Code Online (Sandbox Code Playgroud)

instead.

  • @user63898 `awk` 可以使用 `sub()`、`gsub()` 和(在 GNU awk 上)`gensub()` 函数进行正则表达式搜索和替换,如 `sed`。与捕获组。但是,处理 XML 应该使用 xml 解析器(如 `xmlstarlet` 或 `xq`,或者使用 perl 或 python 或其他任何的 xml 解析库)来完成,而不是使用正则表达式。同样值得一看的是 `xml2`,它将 XML 转换为适合与面向行的工具(如 sed)一起使用的面向行的格式。 (2认同)

Kus*_*nda 5

假设一个格式良好的 XML 文档是这样的:

<root>
<thing  year="2019"
        month="1"
        day="1" />
<thing  year="2020"
        month="5"
        day="13" />
<thing  year="2021"
        month="7"
        day="3" />
</root>
Run Code Online (Sandbox Code Playgroud)

您可以使用如下方式提取每个thing节点的副本,这些节点2020在其year属性中具有该值xmlstarlet

$ xmlstarlet sel -t -c '//thing[@year = "2020"]' -nl file
<thing year="2020" month="5" day="13"/>
Run Code Online (Sandbox Code Playgroud)

请注意,节点内在其属性之间的空白与文档的内容无关。

  • @user63898 然后您可能想要安装它,特别是因为您正在处理 XML 文档。 (2认同)
  • @ user63898 是的,当然,如果您没有钢锯,那么使用锤子作为替代品完全没问题。毕竟,它们都是工具,而你已经有了锤子。 (2认同)