使用 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

所以,我们想删除两个字符串之间的所有数据,这里是 &lt;tag1&gt;&lt;/tag1&gt;。我可以使用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 只是在&lt;tag1&gt; 之后放置一个换行符。 虽然它适用于提供的示例,但请注意有 很多情况下它不能很好地工作(例如,&lt;/tag1&gt; 丢失了)。

【讨论】:

感谢@tshiono,这就像一个魅力!还有一个问题,在您的解决方案中,我们不能使用/**/ 而不是&lt;tag1&gt;&lt;/tag1&gt;,我以这种方式使用它:sed $'s/\/\*/&amp;\\\n/g' input | sed '/\/\*/,/\*\//d' 并得到这个错误:sed: 1: "s//*/&amp;\ , /g": bad flag in substitute command: '&amp;' 感谢您的反馈,但您修改后的代码在我的环境中运行良好。使用 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。我们假设:

您使用 GNU sed, 您可能有其他标签(例如,&lt;tag2&gt;), 同一行可能有多个组 (a&lt;tag1&gt;b&lt;/tag1&gt;c&lt;tag1&gt;d&lt;/tag1&gt;e), 您没有嵌套组 (&lt;tag1&gt;a&lt;tag1&gt;b&lt;/tag1&gt;c&lt;/tag1&gt;), 您所有的&lt;tag1&gt;&lt;/tag1&gt; 都已正确平衡。

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

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

然而,我们可以遍历两个替代命令:一个删除空组 &lt;tag1&gt;&lt;/tag1&gt;,然后是一个删除 &lt;tag1&gt; 之后的任何单个字符,并在删除单个字符时重复:

$ 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#&lt;tag1&gt;&lt;/tag1&gt;##g 删除所有空组。 s#(&lt;tag1&gt;).#\1#g 删除 &lt;tag1&gt; 之后的任何单个字符。 ta 分支到标签 a 如果先前的替换成功。换句话说,我们循环直到没有 换人;在每次迭代中,我们删除所有空组并删除所有非空 &lt;tag1&gt;&lt;/tag1&gt; 对之间的一个字符。当我们停止时,所有组都已被删除。

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

$ 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

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

/&lt;/,/&gt;/d - 现在您的原始代码已实现,因为现在只有一个范围匹配,它会删除该范围并返回其他所有内容。再一次,使用tag1 可以更精确,但再一次就足够了这个实例。

【讨论】:

以上是关于使用 sed 删除两个单词之间的数据的主要内容,如果未能解决你的问题,请参考以下文章

删除字符串中的一个单词(或两个空格之间)

grep 访问多行,查找两个模式之间的所有单词

如何在 shell 脚本中使用 sed 从文件的每一行中删除单词? [复制]

linux中sed命令删除单词

sed:从文件中删除字母数字单词

删除PHP中两个单词之间的空格[重复]