linux三剑客
一、正则表达式
正则表达式(Regular Expression,在代码中常简写为regex、regexp或RE),又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成regex,单数有regexp、regex,复数有regexps、regexes、regexen。
1.1 历史
最初的正则表达式出现于理论计算机科学的自动控制理论和形式化语言理论中。在这些领域中有对计算(自动控制)的模型和对形式化语言描述与分类的研究。
1940年,沃伦·麦卡洛克与Walter Pitts将神经系统中的神经元描述成小而简单的自动控制元。
1950年代,数学家斯蒂芬·科尔·克莱尼利用称之为"正则集合"的数学符号来描述此模型。肯·汤普逊将此符号系统引入编辑器QED,随后是Unix上的编辑器ed,并最终引入grep。自此以后,正则表达式被广泛地应用于各种Unix或类Unix系统的工具中。正则表达式的POSIX规范,分为基本型正则表达式(Basic Regular Expression,BRE)和扩展型正则表达式(Extended Regular Express,ERE)两大流派。在兼容POSIX的UNIX系统上,grep和egrep之类的工具都遵循POSIX规范,一些数据库系统中的正则表达式也匹配POSIX规范。grep、vi、sed都属于BRE,是历史最早的正则表达式,因此元字符必须转译之后才具有特殊含义。egrep、awk则属于ERE,元字符不用转译
Perl的正则表达式源自于Henry Spencer于1986年1月19日发布的regex,它已经演化成了PCRE(Perl兼容正则表达式,Perl Compatible Regular Expressions,一个由Philip Hazel开发的,为很多现代工具所使用的库。
1.2 理论知识
正则表达式可以用形式化语言理论的方式来表达。正则表达式由常量和算子组成,它们分别表示字符串的集合和在这些集合上的运算。给定有限字母表Σ定义了下列常量:
- 空集\varnothing表示集合\varnothing。
- 空串\varepsilon表示集合\{\varepsilon \}。
- 文字字符在\Sigma中的a表示集合\{a\}。
定义了下列运算:
- 串接RS表示集合\{\alpha \beta \mid \alpha \in R,\beta \in S\}。例如:\{ab,c\}\{d,ef\}=\{abd,abef,cd,cef\}。
- 选择R|S表示R和S的并集。例如:\{ab,c\}|\{ab,d,ef\}=\{ab,c,d,ef\}。
- Kleene星号R^*表示包含\varepsilon并且闭合在字符串串接下的R的最小子集。这是可以通过R中的零或多个字符串的串接得到所有字符串的集合。例如:\{ab,c\}^{*}=\{\varepsilon ,ab,c,abab,abc,cab,cc,ababab,\cdots \}。
上述常量和算子形成了克莱尼代数。
很多地方使用对选择使用符号\cup、+或\vee替代竖线。为了避免括号,假定Kleene
星号有最高优先级,接着是串接,接着是并集。如果没有歧义则可以省略括号。例如:(ab)c可以写为abc而a|(b(c*))可以写为a|bc*。
正则表达式为了避免多余的量词,定义了?和+,例如:aa*可以被表达为a+;(a|ε)可以被表达为a?。有时增加补算子\sim; \sim R表示在\Sigma ^{*}上但不在R中的所有字符串的集合。补算子是多余的,因为它可以使用其他算子来表达(尽管计算这种表示的过程是复杂的,而结果可能以指数增大)。
这种意义上的正则表达式可以表达正则语言,精确的是可被有限状态自动机接受的语言类。但是在简洁性上有重要区别。某类正则语言只能用大小指数增长的自动机来描述,而要求的正则表达式的长度只线性的增长。
正则表达式对应于乔姆斯基层级的类型-3文法。但通常编程语言或其相关库(例如PCRE)中实现的正则表达式的表达能力是乔姆斯基层级中类型-3文法的超集[来源请求]。在另一方面,在正则表达式和不导致这种大小上的爆炸的非确定有限状态自动机(NFA)之间有简单的映射;为此NFA经常被用作正则表达式的替表示式。
我们还要在这种形式化中研究表达力。如下面例子所展示的,不同的正则表达式可以表达同样的语言:这种形式化中存在着冗余。
有可能对两个给定正则表达式写一个算法来判定它们所描述的语言是否本质上相等,简约每个表达式到极小确定有限自动机,确定它们是否同构(等价)。
这种冗余可以消减到什么程度?我们可以找到仍有完全表达力的正则表达式的有趣的子集吗?Kleene星号和并集明显是需要的,但是我们或许可以限制它们的使用。这提出了一个令人惊奇的困难问题。因为正则表达式如此简单,没有办法在语法上把它重写成某种规范形式。过去公理化的缺乏导致了星号高度问题。最近Dexter Kozen用克莱尼代数公理化了正则表达式。
很多现实世界的“正则表达式”引擎实现了不能用正则表达式代数表达的特征。
1.3 逻辑原理
先看看出问题的正则:
引起性能问题的关键部分是.*(?:.*=.*)
,这里我们先不管那个非捕获组,将性能问题的正则看做.*.*=.*
。其中.
表示匹配除了换行以外的任意字符(很多人把这里搞错,容易出bug
),.*
表示贪婪匹配任意字符任意次。
- 回溯
在使用贪婪匹配或者惰性匹配或者或匹配进入到匹配路径选择的时候,遇到失败的匹配路径,尝试走另外一个匹配路径的这种行为,称作回溯。
可以理解为走迷宫,一条路走到底,发现无路可走就回到上一个三岔口选择另外的路。
// 性能问题正则
// 将下面代码粘贴到浏览器控制台运行试试
const regexp = `[A-Z]+\\d+(.*):(.*)+[A-Z]+\\d+`;
const str = `A1:B$1,C$1:D$1,E$1:F$1,G$1:H$1`
const reg = new RegExp(regexp);
start = Date.now();
const res = reg.test(str);
end = Date.now();
console.log('常规正则执行耗时:' + (end - start))
现在来看看回溯究竟是怎么一回事:假设我们有一段正则(.*)+\d
,这个时候输入字符串为abcd,注意这个时候仅仅输入了一个长度为4的字符串,我们来分析一下匹配回溯的过程:
上面展示了一个回溯的匹配过程,大概描述一下前三轮匹配。注意(.*)+
这里可以先暂且看成多次执行.*
。(.*){1,}
第一次匹配,因为.*
可以匹配任意个字符任意次,那么这里可以选择匹配空、a、ab、abc、abcd,因为*
的贪婪特性,所以.*
直接匹配了abcd4个字符,+
因为后面没有其他字符了,所以只看着.*
吃掉abcd后就不匹配了,这里记录+
的值为1,然后\d
没有东西能够匹配,所以匹配失败,进行第一次回溯。
第二次匹配,因为进行了回溯,所以回到上一个匹配路径选择的时候,上次.*
匹配的是abcd ,并且路不通,那么这次只能尝试匹配abc
,这个时候末尾还有一个d,那么可以理解为.*
第一次匹配了abc ,然后因为(.*)+
的原因,.*
可以进行第二次匹配,这里.*
可以匹配d ,这里记录+
的值为2,然后\d
没有东西能够匹配,所以匹配失败,进行第二次回溯。
第三次匹配,因为进行了回溯,所以回到上一个匹配路径选择的时候,上次第一个.*
匹配的是abc,第二个.*
匹配的是d
,并且路不通,所以这里第二次的.*
不进行匹配,这个时候末尾还有一个d
,\d
和d
匹配失败,进行第三次回溯。
- 如何减少或避免回溯
优化正则表达式:时刻注意回溯造成的性能影响。
使用DFA正则引擎的正则表达式
- DFA正则引擎
传统正则引擎分为NFA(非确定性有限状态自动机),和DFA(确定性有限状态自动机)。
DFA
对于给定的任意一个状态和输入字符,DFA只会转移到一个确定的状态。并且DFA不允许出现没有输入字符的状态转移
比如状态0,在输入字符A的时候,终点只有1个,只能到状态1。
NFA
对于任意一个状态和输入字符,NFA所能转移的状态是一个非空集合。
比如状态0,在输入字符A的时候,终点可以是多个,即能到状态1,也能到状态0。
DFA和NFA的正则引擎的区别
- DFA
正则里面的DFA引擎实际上就是把正则表达式转换成一个图的邻接表,然后通过跳表的形式判断一个字符串是否匹配该正则。
// 大概模拟一下
function machine(input) {
if (typeof input !== 'string') {
console.log('输入有误');
return;
}
// 比如正则:/abc/ 转换成DFA之后
// 这里我们定义了4种状态,分别是0,1,2,3,初始状态为0
const reg = {
0: {
a: 1,
},
1: {
b: 3,
},
2: {
isEnd: true,
},
3: {
c: 2,
},
};
let status = 0;
for (let i = 0; i < input.length; i++) {
const inputChar = input[i];
status = reg[status][inputChar];
if (typeof status === 'undefined') {
console.log('匹配失败');
return false;
}
}
const end = reg[status];
if (end && end.isEnd === true) {
console.log('匹配成功');
return true;
} else {
console.log('匹配失败');
return false;
}
}
const input = 'abc';
machine(input);
优点:不管正则表达式写的再烂,匹配速度都很快
缺点:高级功能比如捕获组和断言都不支持
- NFA
正则里面NFA引擎实际上就是在语法解析的时候,构造出的一个有向图。然后通过深搜的方式,去一条路径一条路径的递归尝试。
优点:功能强大,可以拿到匹配的上下文信息,支持各种断言捕获组环视之类的功能
缺点:对开发正则功底要求较高,需要注意回溯造成的性能问题
1.4 语法知识
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a到z之间的字母)和特殊字符(称为"元字符")。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
- 为什么使用正则表达式
典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性。若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。
通过使用正则表达式,可以:
数据验证:测试输入字符串是否出现电话号码模式或信用卡号码
替换文本:可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。
基于模式匹配从字符串中提取子字符串:查找文档内或输入域内特定的文本。
- 正则表达式语法
普通字符
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。绝大部分字符都是普通字符。
转义字符
\
是转移字符,其后面的字符会代表不同的意思,转移字符主要有三个作用:
- 是为了匹配不方便显示的特殊字符,比如换行,tab符号等
- 正则中预先定义了一些代表特殊意义的字符,比如\w等
- 在正则中某些字符有特殊含义(比如下面说到的),转义字符可以让其显示自身的含义
转义字符列表:
字符 | 说明 |
---|---|
\cx | 匹配由x指明的控制字符,如\cM匹配一个Control-M或回车符,x的值必须为A-Z或a-z之一,否则将c视为一个原义的'c'字符 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符,如\u00A9匹配版权符号(©) |
\xn | 匹配n,其中n为十六进制转义值,十六进制转义值必须为确定的两个数字长,如'\x41'匹配"A",'\x041'则等价于'\x04'&"1",正则表达式中可以使用ASCII编码 |
\n | 匹配一个换行符,等价于\x0a和\cJ |
\r | 匹配一个回车符,等价于\x0d和\cM |
\f |
匹配一个换页符,等价于\x0c 和\cL |
\t |
匹配一个制表符,等价于\x09 和\cI |
\v |
匹配一个垂直制表符,等价于\x0b 和\cK |
\w |
匹配字母、数字、下划线,等价于'[A-Za-z0-9_] ' |
\W |
匹配非字母、数字、下划线,等价于'[^A-Za-z0-9_] ' |
\s |
匹配任何空白字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v] |
\S |
匹配任何非空白字符,等价于[^\f\n\r\t\v] |
\d |
匹配一个数字字符,等价于[0-9] |
\D |
匹配一个非数字字符,等价于[^0-9] |
\b |
匹配一个单词边界,也就是指单词和空格间的位置,例如'er\b' 可以匹配"never" 中的'er' ,但不能匹配"verb" 中的'er' |
\B |
匹配非单词边界,'er\B' 能匹配"verb" 中的'er' ,但不能匹配"never" 中的'er' |
\\\\ |
匹配\ 本身字符 |
\num |
匹配num ,其中num 是一个正整数,对所获取的匹配的引用,例如,'(.)\1 '匹配两个连续的相同字符 |
字符集和定位字符
字符 | 说明 |
---|---|
[xyz] |
字符集合,匹配所包含的任意一个字符,例如'[abc]' 可以匹配"plain "中的'a ' |
[a-z] |
字符范围,匹配指定范围内的任意字符,例如'[a-z]' 可以匹配'a' 到'z' 范围内的任意小写字母字符 |
[^xyz] |
负值字符集合,匹配未包含的任意字符,例如'[^abc]' 可以匹配"plain" 中的'p' 、'l' 、'i' 、'n' |
[^a-z] |
负值字符范围,匹配任何不在指定范围内的任意字符,例如'[^a-z]' 可以匹配任何不在'a' 到'z' 范围内的任意字符 |
. |
匹配除换行符(\n、\r) 之外的任何单个字符,与上边的区别是是否有[] ,要匹配包括'\n '在内的任何字符,请使用像"(.|\n) "的模式。 |
^ |
匹配输入字符串的开始位置,如果设置了RegExp 对象的Multiline 属性,^ 也匹配'\n '或'\r '之后的位置 |
$ |
匹配输入字符串的结束位置,如果设置了RegExp 对象的Multiline 属性,$ 也匹配'\n '或'\r '之前的位置 |
\b |
匹配一个单词边界,也就是指单词和空格间的位置,例如'er\b '可以匹配"never "中的'er ',但不能匹配"verb "中的'er ' |
\B |
匹配非单词边界,'er\B '能匹配"verb "中的'er ',但不能匹配"never "中的'er ' |
字符数量
字符 | 说明 |
---|---|
{n} |
n 是一个非负整数,匹配确定的n 次,如'o{2} '不能匹配"Bob "中的'o ',但是能匹配"food "中的两个o |
{n,} |
n 是一个非负整数,至少匹配n 次,如'o{2,} '不能匹配"Bob "中的'o ',但能匹配"foooood "中的所有o ,'o{1,} '等价于'o+ ','o{0,} '则等价于'o* ' |
{n,m} |
m 和n 均为非负整数,其中n<=m ,最少匹配n 次且最多匹配m 次,如"o{1,3} "将匹配"fooooood "中的前三个o ,'o{0,1} '等价于'o? ',请注意在逗号和两个数之间不能有空格 |
* |
匹配前面的子表达式零次或多次,例如zo* 能匹配"z "以及"zoo ",* 等价于{0,} |
+ |
匹配前面的子表达式一次或多次,例如'zo+ '能匹配"zo "以及"zoo ",但不能匹配"z ",+ 等价于{1,} |
? |
匹配前面的子表达式零次或一次,例如"do(es)? "可以匹配"do "或"does ",? 等价于{0,1} |
选择,分组,引用和预搜索
字符 | 说明 |
---|---|
x|y |
匹配x 或y ,| 可以多个来组合(a|b|c) ,如'z|food '能匹配"z "或"food ",'(z|f)ood '则匹配"zood "或"food " |
(pattern) |
匹配pattern 并获取这一匹配,所获取的匹配可以从产生的Matches 集合得到,如(abc){2} 匹配abcabc |
\num |
匹配num ,其中num 是一个正整数,对所获取的匹配的引用,如<([a-z]+)><\1> 可以匹配<span></span> 或<div></div> 等 |
(?:pattern) |
匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用">或"字符( |
(?=pattern) |
正向肯定预查,在任何匹配pattern 的字符串开始处匹配查找字符串,这是一个非获取匹配,也就是说该匹配不需要获取供以后使用,例如"Windows(?=95|98|NT|2000) "能匹配"Windows2000 "中的"Windows ",但不能匹配"Windows3.1 "中的"Windows "。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
(?!pattern) |
正向否定预查,在任何不匹配pattern 的字符串开始处匹配查找字符串,这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用,例如"Windows(?!95|98|NT|2000) "能匹配"Windows3.1 "中的"Windows ",但不能匹配"Windows2000 "中的"Windows "。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
(?<=pattern) |
反向肯定预查,与正向肯定预查类似,只是方向相反,例如"(?<=95|98|NT|2000)Windows "能匹配"2000Windows "中的"Windows ",但不能匹配"3.1Windows "中的"Windows " |
(?<!pattern) |
反向否定预查,与正向否定预查类似,只是方向相反,例如"(?<!95|98|NT|2000)Windows "能匹配"3.1Windows "中的"Windows ",但不能匹配"2000Windows "中的"Windows " |
? |
当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m}) 后面时,匹配模式是非贪婪的,非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串,如对于字符串"oooo ",'o+? '将匹配单个"o ",而'o+ '将匹配所有'o ' |
- 常用的正则表达式
校验数字的表达式
- 数字:
^[0-9]*
- n位的数字:
^\d{n}
- 至少n位的数字:
^\d{n,}
- m-n位的数字:
^\d{m,n}
- 零和非零开头的数字:
^(0|[1-9][0-9]*)
- 非零开头的最多带两位小数的数字:
^([1-9][0-9]*)+(.[0-9]{1,2})?
- 带1-2位小数的正数或负数:
^(\-)?\d+(\.\d{1,2})?
- 正数、负数、和小数:
^(\-|\+)?\d+(\.\d+)?
- 有两位小数的正实数:
^[0-9]+(.[0-9]{2})?
- 有1~3位小数的正实数:
^[0-9]+(.[0-9]{1,3})?
- 非零的正整数:
^[1-9]\d*
或^([1-9][0-9]*){1,3}
或^\+?[1-9][0-9]*
- 非零的负整数:
^\-[1-9][]0-9"*
或^-[1-9]\d*
- 非负整数:
^\d+
或^[1-9]\d*|0
- 非正整数:
^-[1-9]\d*|0
或^((-\d+)|(0+))
- 非负浮点数:
^\d+(\.\d+)?
或^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0
- 非正浮点数:
^((-\d+(\.\d+)?)|(0+(\.0+)?))
或^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0
- 正浮点数:
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*
或^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))
- 负浮点数:
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)
或^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))
- 浮点数:
^(-?\d+)(\.\d+)?$
或^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)
校验字符的表达式
- 汉字:
^[\u4e00-\u9fa5]{0,}
- 英文和数字:
^[A-Za-z0-9]+
或^[A-Za-z0-9]{4,40}
- 长度为3-20的所有字符:
^.{3,20}
- 由26个英文字母组成的字符串:
^[A-Za-z]+
- 由26个大写英文字母组成的字符串:
^[A-Z]+
- 由26个小写英文字母组成的字符串:
^[a-z]+
- 由数字和26个英文字母组成的字符串:
^[A-Za-z0-9]+
- 由数字、26个英文字母或者下划线组成的字符串:
^\w+
或^\w{3,20}
- 中文、英文、数字包括下划线:
^[\u4E00-\u9FA5A-Za-z0-9_]+
- 中文、英文、数字但不包括下划线等符号:
^[\u4E00-\u9FA5A-Za-z0-9]+
或^[\u4E00-\u9FA5A-Za-z0-9]{2,20}
- 可以输入含有\^%&',;=?$\"等字符:
[^%&',;=?$\x22]+
- 禁止输入含有~的字符:
[^~\x22]+
特殊需求表达式
- Email地址:
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
- 域名:
[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
- InternetURL:
[a-zA-z]+://[^\s]*
或^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?
- 手机号码:
^(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])\d{8}
(由于工信部放号段不定时,所以建议使用泛解析^([1][3,4,5,6,7,8,9])\d{9}
)- 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):
^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}
- 国内电话号码(0511-4405222、021-87888822):
\d{3}-\d{8}|\d{4}-\d{7}
- 18位身份证号码(数字、字母x结尾):
^((\d{18})|([0-9x]{18})|([0-9X]{18}))
- 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):
^[a-zA-Z][a-zA-Z0-9_]{4,15}
- 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):
^[a-zA-Z]\w{5,17}
- 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}
- 日期格式:
^\d{4}-\d{1,2}-\d{1,2}
- 一年的12个月(01~09和1~12):
^(0?[1-9]|1[0-2])
- 一个月的31天(01~09和1~31):
^((0?[1-9])|((1|2)[0-9])|30|31)
- 钱的输入格式:
- 有四种钱的表示形式我们可以接受:"10000.00"和"10,000.00",和没有"分"的"10000"和"10,000":
^[1-9][0-9]*
- 这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:
^(0|[1-9][0-9]*)
- 一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:
^(0|-?[1-9][0-9]*)
- 这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:
^[0-9]+(.[0-9]+)?
- 必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是"10"和"10.2"是通过的:
^[0-9]+(.[0-9]{2})?
- 这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:
^[0-9]+(.[0-9]{1,2})?
- 这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:
^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?
- 1到3个数字,后面跟着任意个逗号+3个数字,逗号成为可选,而不是必须:
^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?
- 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
- xml文件:
^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]
- 中文字符的正则表达式:
[\u4e00-\u9fa5]
- 双字节字符:
[^\x00-\xff]
(包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))- 空白行的正则表达式:
\n\s*\r
(可以用来删除空白行)- HTML标记的正则表达式:
<(\S*?)[^>]*>.*?</\1>|<.*? />
(网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)- 首尾空白字符的正则表达式:
^\s*|\s\*$
或(^\s*)|(\s*$)
(可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)- 腾讯QQ号:
[1-9][0-9]{4,}
(腾讯QQ号从10000开始)- 中国邮政编码:
[1-9]\d{5}(?!\d)
(中国邮政编码为6位数字)- IP地址:
\d+\.\d+\.\d+\.\d+
(提取IP地址时有用)- IP地址:
((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))
二、grep
命令
参考源码:http://ftp.gnu.org/gnu/grep/
grep
(global search regular expression(RE) and print out the line
:全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
参数选项:
参数 | 说明 |
---|---|
-a |
不要忽略二进制数据 |
-A <显示列数> |
除了显示符合范本样式的那一行之外,并显示该行之后的内容 |
-b |
在显示符合范本样式的那一行之外,并显示该行之前的内容 |
-c |
计算符合范本样式的列数 |
-C <显示列数>或-<显示列数> |
除了显示符合范本样式的那一列之外,并显示该列之前后的内容 |
-d <进行动作> |
当指定要查找的是目录而非文件时,必须使用这项参数,否则grep 命令将回报信息并停止动作 |
-e <范本样式> |
指定字符串作为查找文件内容的范本样式 |
-E |
将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式 |
-f <范本文件> |
指定范本文件,其内容有一个或多个范本样式,让grep 查找符合范本条件的文件内容,格式为每一列的范本样式 |
-F |
将范本样式视为固定字符串的列表 |
-G |
将范本样式视为普通的表示法来使用 |
-h |
在显示符合范本样式的那一列之前,不标示该列所属的文件名称 |
-H |
在显示符合范本样式的那一列之前,标示该列的文件名称 |
-i |
忽略字符大小写的差别 |
-l |
列出文件内容符合指定的范本样式的文件名称 |
-L |
列出文件内容不符合指定的范本样式的文件名称 |
-n |
在显示符合范本样式的那一列之前,标示出该列的编号 |
-q |
不显示任何信息 |
-R/-r : 此参数的效果和指定"-d recurse "参数相同 |
|
-s |
不显示错误信息 |
-v |
反转查找 |
-w |
只显示全字符合的列 |
-x |
只显示全列符合的列 |
-y |
此参数效果跟"-i "相同 |
-o |
只输出文件中匹配到的部分 |
# 在多个文件中查找:
grep "match_pattern" file_1 file_2 file_3 ...
# 输出除之外的所有行-v选项
grep -v "match_pattern" file_name
# 标记匹配颜色--color=auto选项
grep "match_pattern" file_name --color=auto
# 使用正则表达式-E选项
grep -E "[1-9]+"
# 只输出文件中匹配到的部分-o选项
echo this is a test line. | grep -o -E "[a-z]+\."
line.
# 统计文件或者文本中包含匹配字符串的行数-c选项
grep -c "text" file_name
# 输出包含匹配字符串的行数-n选项
grep "text" -n file_name
# 多个文件
grep "text" -n file_1 file_2
# 打印样式匹配所位于的字符或字节偏移:
echo gun is not unix | grep -b -o "not"
7:not
# 解析字段(零宽断言)
echo "background-image: url(/media/images/index/im2.jpg);" | grep -oP '(?<=url\()[^)]+'
三、awk命令
awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。awk语言的最基本功能是将文件逐行读入,以空格为默认分隔符将每行切片,切开部分再基于指定规则浏览和抽取信息。awk抽取信息后,才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息。通常,awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。
awk其名称得自于它的创始人Alfred Aho、Peter Weinberger和Brian Kernighan姓氏的首个字母。实际上AWK的确拥有自己的语言:AWK程序设计语言.三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
基本语法: awk 'pattern { action }' filename
,其中BEGIN
和END
是开始读入和结束读入的控制pattern
。
awk工作流程是这样的:先执行BEGIN
,然后读取文件,读入有\n
换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0
则表示所有域,$1
表示第一个域,$n
表示第n
个域,随后开始执行模式所对应的动作action
。接着开始读入第二条记录······直到所有的记录都读完,最后执行END
操作。参考:FreeBSD Manual Pages:awk
- 调用方式
命令行方式:awk [-F field-separator] 'commands' input-file(s)
,其中commands
是真正awk
命令,[-F
域分隔符]是可选的。input-file(s)
是待处理的文件。在awk
中,文件的每一行中,由域分隔符分开的每一项称为一个域。通常,在不指名-F
域分隔符的情况下,默认的域分隔符是空格。
shell脚本方式:将所有的awk
命令插入一个文件,并使awk
程序可执行,然后awk
命令解释器作为脚本的首行,一遍通过键入脚本名称来调用。相当于shell脚本首行的:#!/bin/sh
,可以换成:#!/bin/awk
。
文件调用方式:将所有的awk
命令插入一个单独文件,然后调用:awk -f awk-script-file input-file(s)
,其中,-f
选项加载awk-script-file
中的awk
脚本,input-file(s)
跟上面的是一样的。
- pattern模式
BEGIN模式
一种特殊的内置模式,在awk处理完所有数据文件之前执行,不需要指定被处理数据文件,直接输出用户设置的内容。在awk中的生命周期只是执行一次。
# 通过BEGIN模式输出字符串
awk 'BEGIN{print "Hello! World."}'
END模式
和BEGIN模式正好相反,它是在awk处理完所有数据文件之后,即将退出程序时成立,在此之前END模式不成立。BEGIN模式也是如此!
#! /bin/awk -f
#输出报表头
BEGIN {
print "scores report"
print "================================="
}
#输出数据
{ print }
#报表完成
END {
print "================================"
print "printing is over"
}
关系表达式
awk
可以支持许多关系运算符,例如>,>=,<,<=,==,!=,!~,~
等。对于数据文件,我们经常需要把比较关系作为匹配模式。
# 打印第2列的成绩超过80的行
awk '$2 > 80 { print }' scores.txt
正则表达式
/regular expression/
匹配方式和上面一致,不过pattern是基于正则表达式的,一般涉及到数据文件中的字符串匹配。
#输出以Tom或者Kon开头的行
awk '/^(Tom|Kon)/ { print }' scores.txt
正则表达式做为匹配模式,一定要把表达式放在两条斜线之间,/regular_expression/
。
混合模式
混合模式就是把关系表达式和正则表达式结合起来,可以使用&&,||, !
来连接,不过它们都需要在单引号以内。
# 混合模式
## 输出以K开头的行,同时第2列分数大于80分的行
awk '/^K/ && $2 > 80 { print }' scores.txt
## 输出以K开头的行或者第2列分数大于80分的行
awk '/^K/ || $2 > 80 { print }' scores.txt
区间模式
用于匹配一段连续的文本行。语法为:awk 'pattern1, pattern2 { actions }' processed datafile
# 区间模式
awk '/^Nancy/, $2==92 { print }' scores.txt
# 区间为:以Nancy开头的行为起始,第2列等于92分的行为终止,输出之间的连续的行
# 注意:当满足patter1或者pattern2的行不只一行的时候,会自动选择第一个符合要求的行。
选择模式
根据条件选择执行的模式,语法:awk 'pattern ? pattern : pattern{actions}' file
# 选择模式
awk '$2>80?a="YES":a="NO"{print $1,a}' scores.txt
BEGINFILE/ENDFILE模式
BEGINFILE/ENDFILE模式是特殊的模式,发生在读每一行之前/之后执行的动作。
action
语句
语句 | 说明 |
---|---|
if( expression ) statement [ else statement ] |
条件语句 |
if ( expression ) statement [ else if (expression1) statement2 [else statement]] |
if-elif-else 语句 |
while( expression ) statement |
while 循环语句 |
for( expression ; expression ; expression ) statement |
for 循环语句 |
for( var in array ) statement |
for 循环语句 |
do statement while( expression ) |
do while 循环语句 |
break |
当break 语句用于while 或for 语句时,导致退出程序循环 |
continue |
当continue 语句用于while 或for 语句时,使程序循环移动到下一个迭代 |
{ [ statement ... ] } |
语句列表 |
expression |
普通的赋值表达式:var = expression |
print [ expression-list ] [ > expression ] |
打印 |
printf format [ , expression-list ] [ > expression ] |
以format 格式打印 |
return [ expression ] |
退出并返回expression | |
next |
语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END 规则,或在END 中应用exit 语句,则终止脚本的执行 |
nextfile |
跳过文件的其他部分,执行next |
delete array[ expression ] |
删除数组元素 |
delete array |
删除所有的数组元素 |
exit [ expression ] |
马上退出,expression 为退出状态 |
- awk内置变量
变量 | 说明 |
---|---|
ARGC |
命令行参数个数 |
ARGV |
命令行参数数组 |
ARGIND |
当前被处理文件的ARGV 标志符 |
CONVFMT |
数字转换格式,默认为"%.6g" |
ENVIRON |
系统环境变量列表,下标为name |
ERRNO |
UNIX系统错误消息 |
FILENAME |
awk 浏览的文件名 |
FIELDWIDTHS |
输入字段宽度的空白分隔字符串 |
FNR |
当前文件的浏览数 |
FS |
输入字段分隔符,默认是空格,可以使用-F参数指定 |
IGNORECASE |
如果为真,则进行忽略大小写的匹配 |
NF |
当前记录中的字段个数,就是有多少列,跟-F参数有关 |
NR |
已经读出的记录数,就是行号,从1 开始 |
OFMT |
数字的输出格式,默认为%.6g |
OFS |
输出字段分隔符,默认也是空格 |
ORS |
输出记录分隔符,默认为换行符 |
RLENGTH |
被匹配函数匹配的字符串长度 |
RS |
输入记录的分隔符,默认为换行符 |
RSTART |
被匹配函数匹配的字符串首位置 |
$0 |
当前记录(作为单个变量) |
$1~$n |
当前记录的第n 个字段,字段间由FS 分隔 |
SUBSEP |
数组下标的分割行符,其默认值为\034 |
awk
内置函数
函数 | 说明 |
---|---|
atan2(y,x) |
返回y/x 的反正切 |
cos(x) |
返回x 的余弦;x是弧度 |
sin(x) |
返回x 的正弦;x是弧度 |
exp(x) |
返回x 幂函数 |
log(x) |
返回x 的自然对数 |
sqrt(x) |
返回x 平方根 |
int(x) |
返回x 的截断至整数的值 |
rand() |
返回(0,1) 的随机数字 |
srand([Expr]) |
将rand 函数的种子值设置为Expr 参数的值,或如果省略Expr 参数则使用某天的时间,返回先前的种子值 |
gsub(Ere,Repl,[In]) |
除了正则表达式所有具体值被替代这点,它和sub 函数完全一样地执行 |
sub(Ere,Repl,[In]) |
用Repl 参数指定的字符串替换为In参数指定的字符串中的由Ere 参数指定的扩展正则表达式的第一个具体值。sub 函数返回替换的数量,出现在Repl 参数指定的字符串中的& 由In 参数指定的与Ere 参数的指定的扩展正则表达式匹配的字符串替换。如果未指定In 参数,缺省值是整个记录($0 记录变量) |
index(String1,String2) |
在由String1 参数指定的字符串(其中有出现String2 指定的参数)中,返回位置,从1开始编号。如果String2 参数不在String1 参数中出现,则返回0 |
length[(String)] |
返回String 参数指定的字符串的长度(字符形式)。如果未给出String参数,则返回整个记录的长度($0 记录变量) |
blength[(String)] |
返回String 参数指定的字符串的长度(以字节为单位)。如果未给出String 参数,则返回整个记录的长度($0记录变量) |
substr(String,M,[N]) |
返回字符串s 中开始位置为M ,长度为N 的子字符串 |
match(String,Ere) |
在String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回Ere 正则表达式出现的位置(字符形式),从1 开始编号,或如果Ere 参数不出现,则返回0 (零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为-1 |
split(String,A,[Ere]) |
将String 参数指定的参数分割为数组元素A[1],A[2],...,A[n] ,并返回nn的值。此分隔可以通过Ere 参数指定的扩展正则表达式进行,或用当前字段分隔符(FS 特殊变量)来进行(如果没有给出Ere参数)。除非上下文指明特定的元素还应具有一个数字值,否则A数组中的元素用字符串值来创建 |
tolower(String) |
返回String参数指定的字符串,字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的LC_CTYPE 范畴定义 |
toupper(String) |
返回String参数指定的字符串,字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的LC_CTYPE 范畴定义 |
sprintf(Format,Expr,Expr,...) |
根据Format参数指定的printf子例程格式字符串来格式化Expr参数指定的表达式并返回最后生成的字符串 |
awk
内置时间函数
函数 | 说明 |
---|---|
systime() |
返回当前时间戳,即指格林威治时间1970-01-01 00:00:00 UTC以来经过的秒数,awk 'BEGIN {print "当前时间的时间戳为:" systime()}' |
mktime(datespec) |
用于将指定格式的时间字符串转换为时间戳,datesspec为时间字符串,符合以下格式YYYY MM DD HH MM SS |
strftime([format [, timestamp[, utc-flag]]]) |
将一个时间戳格式的时间根据指定的时间格式化符转成字符串形式表示 |
awk
位操作
函数 | 说明 |
---|---|
and |
位与操作符号& |
compl |
返回某个数字的按位取反结果,语法格式如下number compl(num) |
lshift(num,n) |
位操作符<< ,高位丢弃,低位补0 |
rshift(num,n) |
位操作符>> ,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
or(num1,num2) |
位操作符| ,当两个数字的二进制格式的相同的位有一个为1则返回1,否则返回0 |
xor(num1,num2) |
位操作符^ ,当两个数字的二进制格式的相同的位相同时返回0,不同则返回1 |
- 某些示例
# 显示时间
echo "4491449987956754" | awk '{print strftime("%Y-%m-%d %H:%M:%d",515483463+rshift($1,22))}'
# 显示文件file的匹配行的第一、二个域加10。
awk '/101/{print $1,$2 + 10}' file
# 只显示最近登陆的五个账号
last -n 5 | awk '{print $1}'
# 只是显示/etc/passwd的账户和账户对应的shell,而账户与shell之间以tab键分割
cat /etc/passwd |awk -F ':' '{print $1"\t"$7}'
# 只是显示/etc/passwd的账户和账户对应的shell,而账户与shell之间以逗号分割,而且在所有行添加列名name,shell,在最后一行添加"blue,/bin/nosh"
cat /etc/passwd |awk -F ':' 'BEGIN {print "name,shell"} {print $1","$7} END{print "blue,/bin/nosh"}'
# 搜索/etc/passwd有root关键字的所有行,并显示对应的shell
awk -F: '/root/{print $7}' /etc/passwd
# 获得linux环境变量(ENVIRON使用)
awk 'BEGIN{print ENVIRON["PATH"];}' /etc/passwd
# 输出数据格式设置:(OFMT使用)
echo 20100117054932 | awk 'BEGIN{OFMT="%.3f";print 2/3,123.11111111; FIELDWIDTHS="4 2 2 2 2 3"}{print $1"-"$2"-"$3,$4":"$5":"$6}'
# 输入和输出分隔符
awk 'BEGIN{FS=":";OFS=" : ";ORS="\n\n"}{print FNR,$1,$NF}' /etc/passwd
# 输入参数获取(ARGC ,ARGV使用)
awk 'BEGIN{FS=":";print "ARGC="ARGC;for(k in ARGV) {print k"="ARGV[k]; }}' /etc/passwd
# 统计/etc/passwd的账户人数
awk 'BEGIN {count=0;print "[start]user count is ", count} {count=count+1;print $0;} END{print "[end]user count is ", count}' /etc/passwd
# 统计某个文件夹下的文件占用的字节数, 以M为单位
ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size/1024/1024,"M"}'
# 统计某个文件夹下的文件占用的字节数,过滤4096大小的文件
ls -l |awk 'BEGIN {size=0;print "[start]size is ", size} {if($5!=4096){size=size+$5;}} END{print "[end]size is ", size/1024/1024,"M"}'
# 三行变1行
awk '{if(NR%3!=0)ORS=" ";else ORS="\n"}1' 5.txt
四、sed
命令
sed
是非交互式的编辑器。它不会修改文件,除非使用shell
重定向来保存结果。默认情况下,所有的输出行都被打印到屏幕上。
sed
编辑器逐行处理文件(或输入),并将结果发送到屏幕。具体过程如下:首先sed
把当前正在处理的行保存在一个临时缓存区中(也称为模式空间),然后处理临时缓冲区中的行,完成后把该行发送到屏幕上。sed
每处理完一行就将其从
临时缓冲区删除,然后将下一行读入,进行处理和显示。处理完输入文件的最后一行后,sed
便结束运行。sed
把每一行
都存在临时缓冲区中,对这个副本进行编辑,所以不会修改原文件。
- 定址
定址用于决定对哪些行进行编辑。地址的形式可以是数字、正则表达式、或二者的结合。如果没有指定地址,sed
将处理输入文件的所有行。
地址是一个数字,则表示行号;"$"符号,则表示最后一行。例如:
sed -n '3p' datafile
则只打印第三行;只显示指定行范围的文件内容,如:
sed -n '100,200p' mysql_slow_query.log
只查看文件的第100行到第200行;地址是逗号分隔的,那么需要处理的地址是这两行之间的范围(包括这两行在内)。范围可以用数字、正则表达式、或二者的组合表示。例如:
sed '2,5d' datafile # 删除第二到第五行
sed '/My/,/You/d' datafile # 删除包含"My"的行到包含"You"的行之间的行
sed '/My/,10d' datafile # 删除包含"My"的行到第十行的内容
- 命令与选项
sed命令告诉sed如何处理由地址指定的各输入行,如果没有指定地址则处理所有的输入行。命令参数:
参数 | 说明 |
---|---|
a\ |
在当前行后添加一行或多行。所追加的文本行位于sed 命令的下方另起一行,多行时除最后一行外,每行末尾需用"\ 续行 |
c\ |
用此符号后的新文本替换当前行中的文本。多行时除最后一行外,每行末尾需用"\ "续行 |
i\ |
在当前行之前插入文本。多行时除最后一行外,每行末尾需用"\ "续行 |
d |
删除行 |
h |
把模式空间里的内容复制到暂存缓冲区 |
H |
把模式空间里的内容追加到暂存缓冲区 |
g |
把暂存缓冲区里的内容复制到模式空间,覆盖原有的内容 |
G |
把暂存缓冲区的内容追加到模式空间里,追加在原有内容的后面 |
l |
列出非打印字符 |
p |
打印行 |
n |
读入下一输入行,并从下一条命令而不是第一条命令开始对其的处理 |
q |
结束或退出sed |
r |
从文件中读取输入行 |
! |
对所选行以外的所有行应用命令 |
s |
用一个字符串替换另一个 |
g |
在行内进行全局替换 |
w |
将所选的行写入文件 |
x |
交换暂存缓冲区与模式空间的内容 |
y |
将字符替换为另一字符(不能对正则表达式使用y 命令) |
sed
命令选项
选项 | 说明 |
---|---|
-e |
进行多项编辑,即对输入行应用多条sed命令时使用 |
-n |
取消默认的输出 |
-f |
指定sed 脚本的文件名 |
退出状态: sed
不向grep
一样,不管是否找到指定的模式,它的退出状态都是0。只有当命令存在语法错误时,sed
的退出状态才不是0。
- 常见例子
# 默认情况下,sed把输入行打印在屏幕上,选项-n用于取消默认的打印操作。
# 当选项-n和命令p同时出现时,sed可打印选定的内容。
# 命令p用于显示模式空间的内容。
sed '/my/p' datafile
# 默认情况下,sed把所有输入行都打印在标准输出上。如果某行匹配模式my, p命令将把该行另外打印一遍。
sed -n '/my/p' datafile
# 选项-n取消sed默认的打印,p命令把匹配模式my的行打印一遍。
# 命令d用于删除输入行。
# sed先将输入行从文件复制到模式空间里,然后对该行执行sed命令,最后将模式空间里的内容显示在屏幕上。如果发出的是命令d,当前模式空间里的输入行会被删除,不被显示。
sed '$d' datafile # 删除最后一行,其余的都被显示
sed '/my/d' datafile # 删除包含my的行,其余的都被显示
sed 's/^My/You/g' datafile # 命令末端的g表示在行内进行全局替换,也就是说如果某行出现多个My,所有的My都被替换为You。
sed -n '1,20s/My$/You/gp' datafile # 取消默认输出,处理1到20行里匹配以My结尾的行,把行内所有的My替换为You,并打印到屏幕上。
sed 's#My#Your#g' datafile # 紧跟在s命令后的字符就是查找串和替换串之间的分隔符。分隔符默认为正斜杠,但可以改变。无论什么字符(换行符、反斜线除外),只要紧跟s命令,就成了新的串分隔符。
sed -e '1,10d' -e 's/My/Your/g' datafile # 选项-e用于进行多重编辑。第一重编辑删除第1-3行。第二重编辑将出现的所有My替换为Your。因为是逐行进行这两项编辑(即这两个命令都在模式空间的当前行上执行),所以编辑命令>的顺序会影响结果。
# r命令是读命令。sed使用该命令将一个文本文件中的内容加到当前文件的特定位置上。
sed '/My/r introduce.txt' datafile # 如果在文件datafile的某一行匹配到模式My,就在该行后读入文件introduce.txt的内容。如果出现My的行不止一行,则在出现My的各行后都读入introduce.txt文件的内容。
sed -n '/hrwang/w me.txt' datafile
# a\ 命令是追加命令,追加将添加新文本到文件中当前行(即读入模式缓冲区中的行)的后面。所追加的文本行位于sed命令的下方另起一行。如果要追加的内容超过一行,则每一行都必须以反斜线结束,最后一行除外。最后一行将以引>号和文件名结束。
sed '/^hrwang/a\
>hrwang and mjfan are husband\
>and wife' datafile
# 如果在datafile文件中发现匹配以hrwang开头的行,则在该行下面追加hrwang and mjfan are husband and wife
# i\ 命令是在当前行的前面插入新的文本。
# c\ 命令,sed使用该命令将已有文本修改成新的文本。
# sed使用该命令获取输入文件的下一行,并将其读入到模式缓冲区中,任何sed命令都将应用到匹配行紧接着的下一行上。
sed '/hrwang/{n;s/My/Your/;}' datafile
# 注:如果需要使用多条命令,或者需要在某个地址范围内嵌套地址,就必须用花括号将命令括起来,每行只写一条命令,或这用分号分割同一行中的多条命令。
# 该命令与UNIX/Linux中的tr命令类似,字符按照一对一的方式从左到右进行转换。例如,y/abc/ABC/将把所有小写的a转换成A,小写的b转换成B,小写的c转换成C。
sed '1,20y/hrwang12/HRWANG^$/' datafile
# 将1到20行内,所有的小写hrwang转换成大写,将1转换成^,将2转换成$。
# 正则表达式元字符对y命令不起作用。与s命令的分隔符一样,斜线可以被替换成其它的字符。
# q命令将导致sed程序退出,不再进行其它的处理。
sed '/hrwang/{s/hrwang/HRWANG/;q;}' datafile
# sed脚本就是写在文件中的一列sed命令。
# 脚本中,要求命令的末尾不能有任何多余的空格或文本。如果在一行中有多个命令,要用分号分隔。
# 执行脚本时,sed先将输入文件中第一行复制到模式缓冲区,然后对其执行脚本中所有的命令。每一行处理完毕后,sed再复制文件中下一行到模式缓冲区,对其执行脚本中所有命令。
# 使用sed脚本时,不再用引号来确保sed命令不被shell解释。
# 搜索/etc/passwd,找到root对应的行,执行后面花括号中的一组命令,每个命令之间用分号分隔,这里把bash替换为blueshell,再输出这行
cat /etc/passwd | sed -n '/root/{s/bash/blueshell/;p}'
# 删除/etc/passwd第三行到末尾的数据,并把bash替换为blueshell
cat /etc/passwd | sed -e '3,$d' -e 's/bash/blueshell/'
五、其他操作
5.2 vim
正则操作
函数式::s/替换字符串/\=函数式
将各行的
id
字符串替换为行号::%s/\<id\>/\=line(".")
将每行开头的单词替换为(行号-10).单词的格式:
:%s/^\<\w\+\>/\=(line(".")-10) .".". submatch(1)
5.3 python
操作
re
库采用raw string
类型来表示正则表达式,表示为r’text’
。raw string
是不包含对转义符的再次转义的字符串,总而言就是string
会对字符转义,而raw string
不会,因为在正则表达中会出现转义符号,所以避免繁琐我们使用raw string
。
Re
库主要功能函数:
re.search(pattern, string, flags=0)
在一个字符串中搜索正则表达式的第一个位置,返回match对象
pattern
:正则表达式的字符串或原生字符串表示
string
:待匹配字符串
flags
:正则表达式使用时的控制标记
re.I
,re.IGNORECASE
:忽略正则表达式的大小写,[A‐Z]
能够匹配小写字符
re.M
,re.MULTILINE
:正则表达式中的^
操作符能够将给定字符串的每行当作匹配开始
re.S
,re.DOTALL
:正则表达式中的.操作符能够匹配所有字符,默认匹配除换行外的所有字符
举例说明:
import re
match = re.search(r'[1-9]\d{5}', 'BIT 100081')
if match:
print(match.group(0))
# 100081
re.match(pattern, string, flags=0)
从一个字符串的开始位置起匹配正则表达式,返回match
对象,参数同search
函数。举例说明:
import re
match = re.match(r'[1-9]\d{5}', 'BIT 100081')
print(match.group(0))
# 报错,match为空
re.findall(pattern,string,flags=0)
搜索字符串,以列表类型返回全部能匹配的子串,参数同search
。举例说明:
import re
ls=re.findall(r'[1-9]\d{5}', 'BIT100081 TSU100084')
print(ls)
# ['100081', '100084']
- re.split(pattern,string,maxsplit=0,flags=0)`
将一个字符串按照正则表达式匹配结果进行分割返回列表类型,参数参考search
maxsplit
:最大分割数,剩余部分作为最后一个元素输出
举例说明:
import re
re.split(r'[1-9]\d{5}', 'BIT100081 TSU100084')
# ['BIT', ' TSU', ' ']
re.split(r'[1-9]\d{5}', 'BIT100081 TSU100084', maxsplit=1)
# ['BIT', ' TSU100081']
re.finditer(pattern,string,maxsplit=0,flags=0)
搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match
对象,参数同split
。 举例说明:
import re
for m in re.finditer(r'[1-9]\d{5}', 'BIT100081 TSU100084'):
if m:
print(m.group(0))
# 100081
# 100084
re.sub(pattern,repl,string,count=0,flags=0)
在一个字符串中替换所有匹配正则表达式的子串返回替换后的字符串:
repl
: 替换匹配字符串的字符串
count
: 匹配的最大替换次数
举例说明:
import re
re.sub(r'[1-9]\d{5}', ':zipcode', 'BIT100081 TSU100084')
# 'BIT:zipcode TSU:zipcode'
Re
库的另一种等价用法(面向对象):rst=re.search(r'[1-9]\d{5}', 'BIT 100081')
函数式的调用,一次性操作:
pat=re.compile(r'[1-9]\d{5}')
rst=pat.search('BIT 100081')
编译后多次操作regex=re.complie(pattern,flags=0)
,regex
也有以上六种用法。
Re
库的Match
对象是一次匹配的结果,包含匹配的很多信息。以下是Match
对象的属性:
.string
: 待匹配的文本
.re
: 匹配时使用的patter
对象(正则表达式)
.pos
: 正则表达式搜索文本的开始位置
.endpos
: 正则表达式搜索文本的结束位置
以下是Match
对象的方法
.group(0)
: 获得匹配后的字符串
.start()
: 匹配字符串在原始字符串的开始位置
.end()
: 匹配字符串在原始字符串的结束位置
.span()
: 返回(.start(), .end())
Re
库默认采用贪婪匹配,即返回匹配最长的子串,最小匹配:
*?
:前一个字符0次或无限次扩展,最小匹配
+?
:前一个字符1次或无限次扩展,最小匹配
??
:前一个字符0
次或1
次扩展,最小匹配
{m,n}?
:扩展前一个字符m
至n
次(含n
),最小匹配只要长度输出可能不同的,都可以通过在操作符后增加
?
变成最小匹配