sed:跳过匹配的第一次出现,然后对于所有其他出现,删除包含匹配的整行

问题描述

我想实现这一目标:

  • 跳过第一次出现的匹配
  • 对于所有其他事件(除了第一次)
  • 删除包含该事件的整行

例如,如果我有这个文本:

<div>
    <p>First text</p>
</div>
<div>
    <p>Second text</p>
    <p>Third text</p>
</div>

我正在匹配 <p>

我希望输出是:

<div>
    <p>First text</p>
</div>
<div>
</div>

我试过 sed '0,/<p>/! /<p>/d',但它输出 unkNown command: `/'

我怎样才能达到我想要的结果? 我还是个新手,所以我的错误可能会显得很愚蠢。 如果您能提供帮助,将不胜感激。

解决方法

从这个问题来看,在我看来,您没有考虑 <p></p> 位于不同行的情况,您甚至都不关心 </p>;您只是删除了所有包含 <p> 的行,除了第一行。

以下命令应该可以完成这项工作:

sed -z 's/<p>/\x0/;s/[^\n]*<p>[^\n]*\n//g;s/\x0/<p>/' input_file

这个解决方案有一个相当简单的逻辑:

  • 它标记并“隐藏”第一个 <p>;
  • 删除所有包含 <p> 的行,除了第一个 <p> 是“隐藏的”;
  • 恢复“隐藏的”<p>

详细说明:

  • 选项 -z 使 Sed 将文件视为由所有行连接而成的单个字符串,每行以 \n 结尾;
  • Sed 命令由以 ; 分隔的 3 部分组成:
    1. s/<p>/\x0/ 将第一个 <p> 更改为 \x0,这不是文件中存在的字符;
    2. s/[^\n]*<p>[^\n]*\n//g 删除(实际上用空字符串替换)任何只包含非\ns 且在某处带有 n<p> 且后跟 \n 的行;包含 <p> 的第一行不会被删除,因为它在步骤 1 之后不包含 <p>
    3. s/\x0/<p>/ 将标记 \x0 改回 <p>
,

当您想保留第二个 <p> 时,它与第一个在同一行,您可以使用

sed -rz ':a;s/(<p>.*\n)[^\n]*<p>[^\n]*\n/\1/;ta' file

当你真的喜欢sed时,你可以使用

sed -n '1,/<p>/p' file; sed '/<p>/d' <(sed '1,/<p>/d' file)

你想要 sed,我也会展示一个 awk 解决方案:

awk '/<p>/ && delp {next}
     /<p>/ {delp=1}
     1' file
,

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

sed '/<p>/{x;/./{x;d};x;h}' file

如果当前行不包含 <p>,则正常打印。

如果当前行包含 <p> 并且保留空间中有副本,则删除当前行。

否则将当前行复制到保留空间并正常打印。


替代方案:

sed -z 's/.*<p>.*\n//2mg' file
,

这是另一种使用相当复杂的逻辑但包含较短命令的解决方案:

sed 'x;s/<p>/&/;x;ta;bb;:a;/<p>/d;:b;H' input_file

这是一个描述逻辑的伪代码:

if one of previous lines contains <p>
    set flag to true
else
    set/leave flag to false
end
if flag
    if line contains <p>
        delete line
    end
end

详细说明:

  • the other answer 不同,它使用 -z 选项,这意味着脚本对输入的每一行都运行文件
  • 脚本执行以下操作(同样,命令由 ; 分隔):
    1. x 交换(exchanges)模式空间的内容(其内容“通常”是正在处理的行)与保持空间的内容(一个可以存储的寄存器)最初为空的东西;请参阅第 7 步以了解我们如何在此脚本中使用它);
    2. s/<p>/&/ 在模式空间的当前内容中搜索<p>,即运行步骤1之前hold空间的内容,并替换为自身(&) ;对于正在处理的文本,这是一个无操作,但它设置为 true 一个内部标志,表示上次执行的 s 命令已成功;实际上,这个 s 命令的作用类似于 如果模式空间包含 <p>,则将标志设置为 true,否则将其保留为 false;
    3. x 交换模式并再次保持空间;这些第一步(1、2 和 3)的最终效果是文本没有改变,如果 保持空间包含<p>;
    4. ta 测试标志,如果为真,则控件移动到 :a 所在的位置;这意味着如果保持空间包含 <p>,我们继续第 5 步,否则我们跳到第 6 步
    5. (紧跟在 :a 之后)/<p>/d 删除当前正在处理的行,如果它包含 <p>;
    6. (如果第 4 步的测试结果是否定的,即保留空间不包含 <p>,我们就在这里)bb 无条件地b牧场(跳转)到 {{ 1}} 是,这意味着我们简单地跳过了第 5 步,即我们让包含 :b 的行离开,但没有删除它;
    7. <p> 将当前模式空间附加到保持空间;实际上,当我们阅读它们时,我们会一行一行地累积到保持空间。
,

你和0,/<p>/! /<p>/d很亲近! /pat//pat/! 后面不能紧跟 // - 您需要 { },因此会出现语法错误。

无需重复 <p> 模式 - 空模式重复使用最后一个。

$ printf "%s\n" a '<p>' c d '<p>' '<p>' '<p>' e | sed '0,/<p>/!{//d}'
a
<p>
c
d
e