使用 sed 删除两个单词之间的数据
Posted
技术标签:
【中文标题】使用 sed 删除两个单词之间的数据【英文标题】:remove data between two word with sed 【发布时间】:2022-01-14 02:56:05 【问题描述】:假设我们有一个包含以下内容的文件:
<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
所以,主要问题是,我们如何删除两个字符串/标签/模式之间的数据,即使它们是单行或多行数据?
谢谢
【问题讨论】:
预期输出是什么? @HatLess data1-5 输入的 XML 是真实有效的吗?如果是这样,您应该探索xpath
解决方案
很遗憾没有,@Fravadona,该文件由不同的数据类型组成,例如 json、html、xml、js 等。
【参考方案1】:
由于sed
无法解析xml文件,很多情况下sed
可以
效果不佳(例如,评论标签中的标签)。
由于sed
正则表达式不支持非贪婪匹配,我们需要
考虑解决方法。
根据以上情况,请您试试:
sed $'s/<tag1>/&\\\n/g' input | sed '/<tag1>/,/<\/tag1>/d'
输出:
data1
data2
data3
data4
data5
第一个sed
只是在<tag1>
之后放置一个换行符。
虽然它适用于提供的示例,但请注意有
很多情况下它不能很好地工作(例如,</tag1>
丢失了)。
【讨论】:
感谢@tshiono,这就像一个魅力!还有一个问题,在您的解决方案中,我们不能使用/*
和*/
而不是<tag1>
和</tag1>
,我以这种方式使用它:sed $'s/\/\*/&\\\n/g' input | sed '/\/\*/,/\*\//d'
并得到这个错误:sed: 1: "s//*/&\ , /g": bad flag in substitute command: '&'
感谢您的反馈,但您修改后的代码在我的环境中运行良好。使用 GNU sed 3.02、4.0 和 4.7 进行测试。所有版本都有效。
作为替代方案,请尝试perl
版本:perl -0777 -pe 's#/\*[\s\S]*?\*/\s*##g' input
。【参考方案2】:
注意:cat input | sed SCRIPT
无用,只需 sed SCRIPT input
。我们假设:
<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>).#\1#g;ta' input
data1
data2data3data4
data5
data6
说明::a
是一个标签,用于循环。 s#<tag1></tag1>##g
删除所有空组。 s#(<tag1>).#\1#g
删除 <tag1>
之后的任何单个字符。 ta
分支到标签 a
如果先前的替换成功。换句话说,我们循环直到没有
换人;在每次迭代中,我们删除所有空组并删除所有非空 <tag1>
、</tag1>
对之间的一个字符。当我们停止时,所有组都已被删除。
如果它留下的空行也应该被删除,我们只需添加一个删除所有空“行”的最终命令。它通过将两个换行符(或模式空间的开头和换行符之间)之间的任何空格字符串(可以为空)替换为单个换行符(或者如果它位于模式的开头则什么都不替换)来实现空间):
$ sed -Ez ':a;s#<tag1></tag1>##g;s#(<tag1>).#\1#g;ta;s#(\`|\n)\s*\n#\1#g' input
data1
data2data3data4
data5
data6
【讨论】:
【参考方案3】:删除范围之前的单行匹配可能会有所帮助,因为如果在第一个匹配之后未找到另一个匹配(在您的情况下为单行匹配),则范围将匹配到文件末尾。
$ sed '/>[a-z0-9]*</d;/</,/>/d' input_file
data1
data2
data3
data4
data5
/>[a-z0-9]*</d
- 这里首先匹配单行。如果需要,它可以被精确定位,但在这种情况下,>
括号就足够了。
/</,/>/d
- 现在您的原始代码已实现,因为现在只有一个范围匹配,它会删除该范围并返回其他所有内容。再一次,使用tag1
可以更精确,但再一次就足够了这个实例。
【讨论】:
以上是关于使用 sed 删除两个单词之间的数据的主要内容,如果未能解决你的问题,请参考以下文章