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)
但是我尝试捕获包含模式的和之间的整个第一段。<>
我在这里做错了什么?
当你想到这个不工作的原因是,<并>没有需要在正则表达式来进行转义做,他们没有任何特殊含义。然而,\<和\> 你有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)时的默认行为是打印当前记录,这将打印出您需要的内容。
首先,大多数文本处理工具,例如sed或awk逐行工作,因此匹配整个段落需要一点额外的努力。这是可能的,但这也是您看到的意外输出的原因之一。
其次,由于 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.
假设一个格式良好的 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)
请注意,节点内在其属性之间的空白与文档的内容无关。