使用sed去除maven项目中xml文件中的注释-支持单行&多行处理
摘要
场景:
CI/CD或者项目维护时需要做一些自动化的脚本处理.
现在要提取Maven
工程中的pom.xml
文件中的引用jar包的版本定义.
此时我们需要能够使用脚本比较方便的直接提取项目中的版本号定义
如: 下面的pom
文件中.我们希望比较快速的提取出变量:leo.pdf.version
的值
<properties>
<leo.pdf.version>1.0.2</leo.pdf.version>
</properties>
此时需要准确为别标签
properties
内部的内容. 同时还要对里面可能存在的注释进行识别. 以便进行区分过滤.最终得到一个真正的版本定义.
可能遇到的问题:
- 被注释掉的内容
<!-- <leo.pdf.version>1.0.2</leo.pdf.version> -->
- 正文部分后面包含注释内容
<leo.pdf.version>1.0.2</leo.pdf.version><!-- 跟在右边的注释-->
- 多行注释
<leo.pdf.version>1.0.2</leo.pdf.version>
<!-- 第一行
第二行
跟在右边的注释
-->
- 行内多注释注释
<leo.pdf.version>1.0.2</leo.pdf.version><!-- 第一个注释 --> <leo.auth.version>1.0.0</leo.auth.version><!-- 第二个注释 -->
虽然这些问题可以规范大家的编码规则 .但是对于一个写自动化处理脚本来说,还是要尽可能的兼容所有的场景才是.
解决方案
想到过的两个正统的处理办法. 那就是借助于非贪婪匹配模式 和 反向(负向)匹配模式
问题与对策
-
行内注释(单个)
- 直接使用
s
命令进行替换即可.
- 直接使用
-
多行注释(注释跨行的场景)
- 使用
sed
多行处理技术
- 使用
-
一行多注释 (注释之间有有效内容, 如
<!-- comment1 --> content <!-- comment2 -->
)-
这个是难点. 如果直接使用
s
命令.sed 中的正则匹配只有贪婪模式(greedy mode)因此如果直接匹配,会导致正文内容被错误匹配吞噬 -
比如:
下面的代码如果直接使用命令:
sed -E 's/<!--.*-->//'
进行替换的话. 正文中的版本号1.0.2
也会被过滤掉.<properties> <!-- comment1 --> <leo.pdf.version>1.0.2</leo.pdf.version> <!-- comment2 --> </properties>
此时需要用到一些比较取巧的办法
在
sed
中,默认的s
命令是只会匹配并替换第一个匹配项.
这个有什么用呢.注意我们的匹配项是<!--.*-->
期中的结束字符是-->
,我们希望这个匹配是非贪婪的(non greedy).但是sed
不存在这种模式. 那我们就希望有没有变通的方式来完成这个处理. 以上字符的模式可以表述为:以<!--
开关,并且以-->
结尾的字符.重点是以-->
结尾.在贪婪模式时,中间的.*
会尽可能多的匹配通用字符串.这样-->
就会匹配到最后一个出现的-->
;此时考虑到我们匹配的重点是- 结束字符串
-->
s/<--.*-->//
中间的.*
是贪婪的s
命令是在非g
模式下,是 "非贪婪的",亦即:s/xx//
这样的表述只会匹配并替换第一次出现的pattern- 结合以上三点.我们是不是有可能把
-->
先替换成一个特殊的串.一个不会出现在原文中的字符串.此时的替换不使用/g
模式.那我们替换的一定是第一个出现的-->
.那此时再使用上面的命令2进行替换时.(此时的结束符号需要用刚替换的特殊字符串代替)这样我们就可以变相的实现非贪婪模式的匹配. - 实现完了大概是这个样子.
sed -E 's/-->/magicword/' -e 's/<!-.*magicwords//'
这样就可以变相的实现非贪婪模式; - 注: 这个能这样处理的前提是在正常的标签中,以及注释中不能嵌套
-->
,否则是不能这样处理的.但是对于这个思路肯定是通用的. 2
只解决了非贪婪的模式.但是对于一行有多个注释的问题. 上面只替换了第一个.若要替换多个.还需要另外的技术:循环,由于刚刚的命令是每次只处理一个注释. 因此可以配合着循环指令
实现递归的处理.每一次循环处理一个注释.直到当前已经读取的行没有完整的注释为止.
- 结束字符串
-
下面给出代码:
#!/usr/bin/env bash
# 这里有一点比较取巧的地方. 由于
# 1. sed 没有反向匹配. 比如不包含子串:`hello`的任意串.
# 2. sed 没有非贪婪模式. 比如你想要 basketballfootball. 这里想取出b开头,ball结束的串. b.*?ball . 这个?就是非贪婪模式. 最短匹配.这样就可以取出. 否则取不出.
# 流程:
# 1) 首先寻找开始符号. <!--
# 2) 然后寻找结束符号. --> . 如果当前行不是结束符号,就接着读下一行.直到找到结束符号.
# 3) 寻找到了结束符号: --> 时,不能直接使用: s/<!--.*-->// ,也不能直接使用: s/<!--.*-->//g ,当一行有多个注释的时候.第一种和第二种都会由于贪婪模式而把两个注释之间的正文给清除掉.
# 如: <!-- some comment --> 这中间夹着正文. <!-- another comment --> 上面的两种都会直接把夹杂着的正文带走.
# 应该先使用:
# s/-->/mockend1024/
# 把*第一个* --> 给替换成一个临时字符串. 这里有两个关键点:
# - 这里是匹配结束的后缀串. 而s操作.如果不加g.则只会匹配第一个.这个是关键点.
# - 结束后缀串被临时替换为了一个不可能存在(也有可能存在,只是正常业务不会有). 此时再进行操作去除注释的时候. 中间的.*虽然是贪婪的.但是结束符号(替换后的临时的)却只有一个. 因此变相的实现了非贪婪的.
gsed -E -e\
':start
/<!--/ {
:loop
/-->/ {
s/-->/mockend102499883356/
s/<!--.*mockend102499883356//
/<!--/ {
b loop
}
b done
}
:add
N
b loop
:done
}' pom.xml;
注:
- 代码里面使用了分支循环结构.
sed
不像一般的语言支持完整的分支处理. 它只能进行简单的分支处理.b
是无条件跳转.t
是s
命令执行成功后跳转.一般用于循环,具体还要看实际语境;T
是与t
一样.不过是否跳转的判断条件刚好相反.- 代码里面第一个
b loop
前面没有判定s
命令的执行情况.是因为其语句是一定会执行成功的.两个s实际可以看作成一个原子的操作. 一个成功,另外一个必定成功.- 同时:
/<--/ {xxx}
可以看作是if (StirngInBuffer.contain("<--")){}
这样的一个条件语句.既是一个简单的正则,又可以看作是普通的字符串包含关系.
我们还是拆解解释一下这个语句.
:start # 定义一个起始位置的标签.方便后序的程序进行跳转,
# 如果用得到的话. (当缓冲区还有未匹配完成的半串,
# 然后读下一行的时候如果需要就可能跳回来)
# 否则代码也会回到这执行.但是缓存区的内容会被清空了. 可能会存在无法完成匹配的情况.
# 当然,目前使用的算法是没有这种情况的. (前提是XML的格式是正确完整的)
/<!--/ { # 当(模式空间里面的内容)匹配到一个正确的注释开始串时,
# 准确的说是模式空间中的内容包含这个子串时,执行这个一个{}内的指令.
# 具体的sed的基本工作原理可以参考下面的英文引用.
:loop # 定义一个用于for循环的loop标签
/-->/ { # 如果模式空间中的内容包含 注释的结束符号. 执行子{}中的内容 .相当于是两层if嵌套.
# 此时,一定是满足两个条件的.
s/-->/mockend102499883356/ # 先执行magicstring的替换.
# 这样只会优先替换第一个发现的 `-->`
s/<!--.*mockend102499883356// # 此时在进行贪婪匹配的时候,变相实现了非贪婪模式.
# 此时会把模式空间中的第一个完整的注释删除掉.
/<!--/ { # 再进行一次判定是包含 注释前导符号. 如果有需要回到第2层if那. 也就是一判定注释后缀串的地方. 回到那时就好像没有进行过删除的刚进入时的时候一样. (实际判定是否包含结束符号是一的.只是没有开始就一定没有结束,但是没有结束,却可能还包含开始. 所以目前这样写是最简洁的. )
b loop
}
b done # 上面的没有检测到开始符号.说明模式空间的内容可以暂时先清空结束了.
}
:add
N # 上面第一个if块.没有执行的时候会执行此语句.相当于那if的 else : 读入下一行到缓冲区
b loop # 返回到for的开始
:done # 程序结束标签
}
6.1 How
sed
Works
sed
maintains two data buffers: the active pattern space, and the auxiliary hold space. Both are initially empty.
sed
operates by performing the following cycle on each line of input: first, sed
reads one line from the input stream, removes any trailing newline, and places it in the pattern space. Then commands are executed; each command can have an address associated to it: addresses are a kind of condition code, and a command is only executed if the condition is verified before the command is to be executed.
When the end of the script is reached, unless the -n option is in use, the contents of pattern space are printed out to the output stream, adding back the trailing newline if it was removed.^8^ Then the next cycle starts for the next input line.
Unless special commands (like ‘D’) are used, the pattern space is deleted between two cycles. The hold space, on the other hand, keeps its data between cycles (see commands ‘h’, ‘H’, ‘x’, ‘g’, ‘G’ to move data between both buffers).
其它尝试
为了避免sed
的贪婪模式 , 期间尝试过如: s/<!--[^(-->)]*?-->
这样的模式去企图让sed
去使用一个不可能的非贪婪模式和反向匹配 (这里虽然不是直接的反向匹配 , 企图把反向匹配转换为一个不包含某字符串的字符集这个实际也是做不到的. 并且一些特殊字符在字符集里面也会失去特殊含义.所以这个根本不可能得到我们想要的效果.以至于这个在测试时会让人得到一些莫名其妙的结果.好像可以,又好像不可以.让人不知问题出在哪.)
- sed - Execution-Cycle (# sed, a stream editor)
- 正则表达式匹配不包含某些字符串的技巧
- 【正则表达式系列】贪婪与非贪婪模式
- how to delete comment in xml with bash - google search
- How to remove XML-like tags from input?
- How to remove comment from XML using shell script (这里有我的解决办法的回复.)
- Remove XML comments using Regex in bash
- Remove sections of XML file that not matches criteria - using bash
- 正则表达式删除代码的注释
- 正则表达式删除XML文件中的注释
- use regex to find comment in xml
- Regex to match XML comments - 比较好的解决,但是不适用于SED
- Remove sections of XML file that not matches criteria - using bash
- How to remove a newline from a string in Bash
- Recursively remove XML comments 非常重要,此篇文章的解决思路来源之一,感谢
- How to remove XML-like tags from input?
- why sed regex Non greedy
- Non-greedy match with SED regex (emulate perl's .*?)
- How to make sed do non-greedy match? [duplicate]
- Non greedy (reluctant) regex matching in sed?
- # 正则表达式30分钟入门教程
- # Unix / Linux - Regular Expressions with SED
- # 与SED正则表达式的非贪婪匹配(模拟perl的。*?)
- sed: Can my pattern contain an "is not" character? How do I say "is not X"?
- sed don't contain substring
- Sed regex and substring negation
- # 【Java】正则表达式(不)包含某个字符串,(不)以某字符串开头
- Regular expression to match a line that doesn't contain a word
- # 在sed中非贪婪(不情愿)的正则表达式匹配?
- sed的正则匹配如何实现非贪婪?
- ### Removing singleline and multiline comments from XML files.