使用 sed 删除两个单词之间的数据

remove data between two word with sed

假设我们有一个包含以下内容的文件:

<tag1>
junk1
junk2
</tag1>
data1
data2
data3
<tag1>junk3</tag1>
data4
data5

所以,我们要删除两个字符串之间的所有数据,即 <tag1></tag1>。我可以使用 sed 命令完成这项工作,例如:

cat input | sed '/<tag1>/,/<\/tag1>/d'

但是有一个问题,命令不能正常运行,输出中去掉了一行tag1标签后的数据。上述命令的输出:

data1
data2
data3

所以,主要问题是,我们如何删除两个 strings/tags/patterns 之间的数据,即使它们是单行或多行数据?

谢谢

  • 由于sed无法解析xml文件,所以sed有很多情况 效果不佳(例如评论标签中的标签)。
  • 由于sed正则表达式不支持非贪婪匹配,我们需要 考虑变通办法。

基于以上,请你试试:

sed $'s/<tag1>/&\\n/g' input | sed '/<tag1>/,/<\/tag1>/d'

输出:

data1
data2
data3
data4
data5

第一个 sed 只是在 <tag1> 之后换行。
虽然它适用于提供的示例,但请注意有 许多情况下效果不佳(例如缺少 </tag1>)。

注:cat input | sed SCRIPT没用,直接sed SCRIPT input。让我们假设:

  • 你使用 GNU sed,
  • 您可能还有其他标签(例如,<tag2>),
  • 您可能在同一行上有多个组 (a<tag1>b</tag1>c<tag1>d</tag1>e),
  • 您没有嵌套组 (<tag1>a<tag1>b</tag1>c</tag1>),
  • 你所有的 <tag1></tag1> 都得到了适当的平衡。

GNU sed 有简洁的 -z 选项,将 NUL 字符视为行终止符,而不是换行符。因此,由于您的输入文件不包含任何 NUL 字符,因此可以将其内容视为一个字符串(其中包含换行符)。

因此,我们可以开始删除 <tag1>...</tag1> 个组,而无需考虑它们是否在同一“行”上。但是由于 sed 是贪婪的,我们不能简单地 s#<tag1>.*</tag1>##g 因为它会删除第一个 <tag1> 和最后一个 </tag1> 之间的所有内容:如果你有多个组,它也会删除组之间的文本.

然而,我们可以循环执行两个替代命令:一个删除空组 <tag1></tag1>,然后一个删除 <tag1> 之后的任何单个字符,并且只要删除单个字符就重复:

$ cat input
<tag1>
junk1
junk2
</tag1>
data1
data2<tag1>junk3</tag1>data3<tag1>junk4</tag1>data4
data5
<tag1>junk5</tag1>
data6
<tag1>junk6</tag1>
$ sed -Ez ':a;s#<tag1></tag1>##g;s#(<tag1>).##g;ta' input

data1
data2data3data4
data5

data6

说明::a是一个标签,用于循环。 s#<tag1></tag1>##g 删除所有空组。 s#(<tag1>).##g 删除 <tag1> 之后的任何单个字符。如果先前的替换成功,ta 分支到标签 a。换句话说,我们循环直到没有 换人;在每次迭代中,我们删除所有空组并删除所有非空 <tag1></tag1> 对之间的一个字符。当我们停止时,所有的组都被删除了。

如果它留下的空行也应该被删除,我们只需添加一个删除所有空“行”的最后命令。它通过用单个换行符(或什么都没有)替换两个换行符之间(或模式 space 和换行符之间的模式 spaces (可以为空)的任何字符串来实现如果它在模式的开头 space):

$ sed -Ez ':a;s#<tag1></tag1>##g;s#(<tag1>).##g;ta;s#(\`|\n)\s*\n##g' input
data1
data2data3data4
data5
data6

删除范围之前的单行匹配可能会有所帮助,因为如果在第一个匹配之后没有找到另一个匹配,范围将匹配到文件末尾,在您的情况下,是单行匹配。

$ sed '/>[a-z0-9]*</d;/</,/>/d' input_file
data1
data2
data3
data4
data5

/>[a-z0-9]*</d - 这里先匹配单行。如果需要,它可以精确定位,但 > 括号在这种情况下就足够了。

/</,/>/d - 现在您的原始代码已实现,因为现在只有一个范围匹配,它删除了该范围和 returns 一切 else.Once 再次,它可以更精确使用 tag1 但在这种情况下再一次就足够了。