使用bash工具从xml中删除标记

时间:2014-09-04 08:06:25

标签: xml bash awk sed

问题

我有一个以

格式创建日志的应用程序
2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>111</e></d><d><e>123</e></d><d><e>222</e></d><d><e>333</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4

使用bash工具我想删除所有没有后代的标记<e>123</e>

到这种形式

2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>123</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4

我尝试使用awk和sed这样做,但我失败了。请帮助编写脚本或其他可以执行此操作的工具的说明。

信息(从评论中移出)

目前我有这样一个(我发现最好的)解决方案。“

echo '2014-09-01 12: 01: 01.899;some app logs 2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>111</e></d><d><e>123</e></d><d><e>222</e></d><d><e>333</e><‌​/d></c></b></a>;some app logs3 2014-09-01 12: 01: 03,625;some app logs4' | awk '{print "<d" $0}' RS="<d" | sed -n '1 s/^<d// ; /^<d/ ! p; /^<d.*>123</ p'

此致

Krzysiek

4 个答案:

答案 0 :(得分:3)

试试这个:

$ awk -v t="<d><e>123</e></d>" '{gsub(t,RS); gsub("<d><e>[^<]+</e></d>",""); gsub(RS,t)}1' file
2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>123</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4

以上简单地接受输入文件的每一行,并用换行符替换所有出现的目标字符串<d><e>123</e></d>(显然不能出现在原始行中),然后删除匹配的所有其他字符串{{ 1}},然后用目标字符串替换所有换行符(即将我们之前添加的换行符恢复为原始值)。

如果这不是您想要的,请编辑您的问题以澄清您的要求并提供更具代表性的示例。

答案 1 :(得分:2)

你可以通过perl来完成这个,

$ perl -pe 's/<e>(?:(?!\b123\b).)*?<\/e>//g; s/<([^><]*)><\/\1>//g' file
2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>123</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4

<强>解释

  • <e>(?:(?!123).)*?<\/e>匹配<e>以外的所有<e>123</e>代码。在第一部分中,删除了所有匹配的<e>代码。
  • 第二部分<([^><]*)><\/\1>将删除所有具有直接结尾的标记。(即,开头标记后面紧跟一个结束标记)

这会删除所有不包含确切字符串<e>的{​​{1}}代码。

123

示例:

perl -pe 's/(?:(?!<e>123<\/e>)<e>.*?<\/e>)//g; s/<([^><]*)><\/\1>//g' file

$ cat file
2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>111</e></d><d><e>123</e><e>1234</e><e>123:4</e></d><d><e>222</e></d><d><e>333</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4

答案 2 :(得分:1)

也许这个awk例子可以引导你朝着正确的方向前进:

$ awk -F';' '{gsub("<d><e>[^0-9]*</e></d>", "", $3)} {print}' some.log
2014-09-01 12: 01: 01.899; And, some app logs
2014-09-01 12: 01: 02,045  And, some app logs2 <a><b><c><d><e>123456789</e></d></c></b></a> some app logs3
2014-09-01 12: 01: 03,625; And, some app logs4

解释
-F';'字段分隔符是分号
如果标记gsub("<d><e>[^0-9]*</e></d>", "", $3)中第3列中的数据不是数字,则<e>执行全局替换

答案 3 :(得分:1)

以下假设输入文本位于名为&#39; test.log&#39;的文件中。并且你想要一个解决方案,你要输入和输出输入(即cat&#39; test.log&#39;而不是指定它作为输入)。

使用占位符值:

如果您尝试使用正则表达式来处理与您希望保留的模式非常相似的所有内容,则通常更容易首先更改您希望不对占位符值进行操作的文本区别于你希望采取行动的模式:

cat test.log | sed -e "s/Q/Qz/g" -e "s/<e>123<\/e>/Qa/g" -e "s/<e>[^<]*<\/e>//g" -e "s/Qa/<e>123<\/e>/g" -e "s/Qz/Q/g" -e "s/<[^e]>[^<]*<\/[^e]>;\?//g" -e "s///g" -e "s///g" -e "s///g" -e "s///g" -e "s///g"

诀窍在于意识到您正在操作的数据不必保留您正在操作的中间形式中的形式。重要的只是输出。因此,数据的转换是:

输入(添加了标签中根本没有<e>123</e>的行。这是我们可能需要处理的情况):

2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>111</e></d><d><e>123</e></d><d><e>222</e></d><d><e>333</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4
2014-09-01 12: 01: 04,045;some app logs5;<a><b><c><d><e>111</e></d><d><e>222</e></d><d><e>333</e></d></c></b></a>;some app logs6

中介形式1(在sed中逐行存在):与输入相同,因为没有&#34; Q&#34;存在于测试数据中。

中介表格2(在sed内):我们要保留给占位符的文本:

2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>111</e></d><d>Qa</d><d><e>222</e></d><d><e>333</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4
2014-09-01 12: 01: 04,045;some app logs5;<a><b><c><d><e>111</e></d><d><e>222</e></d><d><e>333</e></d></c></b></a>;some app logs6

中介表格3(删除不包含123的<e></e>代码):

2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d></d><d>Qa</d><d></d><d></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4
2014-09-01 12: 01: 04,045;some app logs5;<a><b><c><d></d><d></d><d></d></c></b></a>;some app logs6

中介表格4(从占位符替换<e>123</e>):

2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d></d><d><e>123</e></d><d></d><d></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4
2014-09-01 12: 01: 04,045;some app logs5;<a><b><c><d></d><d></d><d></d></c></b></a>;some app logs6

中介表格5(不清楚占位符):(与表格4相同,因为没有&#34; Q&#34;)。

输出(替换后删除空标签):

2014-09-01 12: 01: 01.899;some app logs
2014-09-01 12: 01: 02,045;some app logs2;<a><b><c><d><e>123</e></d></c></b></a>;some app logs3
2014-09-01 12: 01: 03,625;some app logs4
2014-09-01 12: 01: 04,045;some app logs5;some app logs6

假设我们不应该留下一些应用程序log5 ;;一些app log6&#34;但是&#34;一些应用程序日志5;一些应用程序日志6&#34;。如果不是这种情况,可以调整正则表达式。

使用占位符时的问题

如果您的占位符不是唯一的,那么当从占位符更改回来时,您会破坏数据。要在未知输入数据中使用唯一占位符,您必须花费替换来清除占位符的任何当前使用,并使用替换来还原清除它。要执行此操作,您可以使用替换,例如:sed -e "s/Q/Qz/g"这样就不会导致在文本中以Q开头的任何两个字母组合,而不是&#34; Qz&#34;。然后你有大量潜在的独特双字母占位符(例如&#34; Qa&#34;,&#34; Qb&#34;,&#34; Qc&#34;,&#34; QA&#34;等)。完成使用后,您可以通过撤消替换来更改回文本:sed -e "s/Qz/Q/g"如果有多个唯一占位符,则可以使用它们来表示多个其他字符串。使用这种方法时,您必须记住所有与文本匹配的操作,同时使用已执行初始许可的占位符。

在某些情况下,如果您了解输入数据的特征,则可以选择永远不会出现在该数据中的占位符。这可以节省两个替换操作的CPU成本以及清除两个字符占位符所需的潜在额外内存。但是,对于日志文件,您要查找的内容之一是损坏,因此使用您只假设不在数据中的短占位符是一个坏主意。

如果您不知道所包含字符的确切输入,但您确实知道输入的某些特征,那么您可以选择使用占位符来保存这两个替换,占位符非常不太可能存在于您的输入中,但不保证是独一无二的。这确实带来了一些风险。在这种情况下,用于占位符的字符串越复杂,并且它与可能输入的内容越相似,您选择输入中存在的占位符的风险就越小。

对于这个例子,文本&#34; lOnG3Rep5LacEN2eV7E9rE4xIST&#34; 非常不太可能存在于输入日志文件中,即使它已损坏。

以下假设输入文本位于名为&#39; test.log&#39;的文件中。为了方便。此外,它假设&#34; lOnG3Rep5LacEN2eV7E9rE4xIST&#34;将不会存在于输入中。实际上用于中间字符串的内容当然可以是您想要的任何唯一内容:

cat test.log | sed -e "s/<e>123<\/e>/lOnG3Rep5LacEN2eV7E9rE4xIST/g" -e "s/<e>[^<]*<\/e>//g" -e "s/lOnG3Rep5LacEN2eV7E9rE4xIST/<e>123<\/e>/g" -e "s/<[^e]>[^<]*<\/[^e]>;\?//g" -e "s///g" -e "s///g" -e "s///g" -e "s///g" -e "s///g"

选择使用您未保证的占位符在输入数据中不存在是一种风险。除非您了解风险并选择接受风险,否则不应这样做。当输出将立即由能够发现任何此类问题的人员进行审查时,接受这种风险会更加合理。

感谢Ed Morton提醒我,我已经养成了在没有充分考虑的情况下接受这种风险的习惯。

使用正则表达式来定义某些内容不是:

字符:

因为模式&#34; 123&#34;非常简单和准确,定义一个匹配之外的所有字符串的正则表达式相对容易。请注意,对于您尝试从匹配中排除的更复杂的模式,这会变得更加复杂:

cat test.log | sed -e "s/<e>\(\|[^1<][^<]*\|1[^2<][^<]*\|12[^3<][^<]*\)<\/e>//g" -e "s/<[^e]>[^<]*<\/[^e]>;\?//g" -e "s///g" -e "s///g" -e "s///g" -e "s///g" -e "s///g"

这会构建一个带有子模式的正则表达式,这些子模式会逐渐将所有内容与一个字符逐渐匹配,而不是您希望不匹配的模式。

负面展望/展望未来:

正则表达式语法的许多实现提供负向前瞻或后视运算符。这些可用于生成更复杂的&#34;而不是此字符串&#34;。