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 理论知识

正则表达式可以用形式化语言理论的方式来表达。正则表达式由常量和算子组成,它们分别表示字符串的集合和在这些集合上的运算。给定有限字母表Σ定义了下列常量:

定义了下列运算:

上述常量和算子形成了克莱尼代数。

很多地方使用对选择使用符号\cup+\vee替代竖线。为了避免括号,假定Kleene星号有最高优先级,接着是串接,接着是并集。如果没有歧义则可以省略括号。例如:(ab)c可以写为abca|(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,\dd匹配失败,进行第三次回溯。

优化正则表达式:时刻注意回溯造成的性能影响。

使用DFA正则引擎的正则表达式

传统正则引擎分为NFA(非确定性有限状态自动机),和DFA(确定性有限状态自动机)。

DFA

对于给定的任意一个状态和输入字符,DFA只会转移到一个确定的状态。并且DFA不允许出现没有输入字符的状态转移

比如状态0,在输入字符A的时候,终点只有1个,只能到状态1。

NFA

对于任意一个状态和输入字符,NFA所能转移的状态是一个非空集合。

比如状态0,在输入字符A的时候,终点可以是多个,即能到状态1,也能到状态0。

DFA和NFA的正则引擎的区别

正则里面的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引擎实际上就是在语法解析的时候,构造出的一个有向图。然后通过深搜的方式,去一条路径一条路径的递归尝试。

优点:功能强大,可以拿到匹配的上下文信息,支持各种断言捕获组环视之类的功能

缺点:对开发正则功底要求较高,需要注意回溯造成的性能问题

1.4 语法知识

正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a到z之间的字母)和特殊字符(称为"元字符")。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。

典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性。若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。

通过使用正则表达式,可以:

数据验证:测试输入字符串是否出现电话号码模式或信用卡号码

替换文本:可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。

基于模式匹配从字符串中提取子字符串:查找文档内或输入域内特定的文本。

普通字符

普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。绝大部分字符都是普通字符。

转义字符

\是转移字符,其后面的字符会代表不同的意思,转移字符主要有三个作用:

  1. 是为了匹配不方便显示的特殊字符,比如换行,tab符号等
  2. 正则中预先定义了一些代表特殊意义的字符,比如\w等
  3. 在正则中某些字符有特殊含义(比如下面说到的),转义字符可以让其显示自身的含义

转义字符列表:

字符 说明
\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} mn均为非负整数,其中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 匹配xy,|可以多个来组合(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'

校验数字的表达式

校验字符的表达式

特殊需求表达式

二、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,其中BEGINEND是开始读入和结束读入的控制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)跟上面的是一样的。

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模式是特殊的模式,发生在读每一行之前/之后执行的动作。

语句 说明
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语句用于whilefor语句时,导致退出程序循环
continue continue语句用于whilefor语句时,使程序循环移动到下一个迭代
{ [ 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为退出状态
变量 说明
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
函数 说明
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参数指定的表达式并返回最后生成的字符串
函数 说明
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]]]) 将一个时间戳格式的时间根据指定的时间格式化符转成字符串形式表示
函数 说明
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命令)
选项 说明
-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/替换字符串/\=函数式

  1. 将各行的id字符串替换为行号::%s/\<id\>/\=line(".")

  2. 将每行开头的单词替换为(行号-10).单词的格式::%s/^\<\w\+\>/\=(line(".")-10) .".". submatch(1)

5.3 python操作

re库采用raw string类型来表示正则表达式,表示为r’text’raw string是不包含对转义符的再次转义的字符串,总而言就是string会对字符转义,而raw string不会,因为在正则表达中会出现转义符号,所以避免繁琐我们使用raw string

Re库主要功能函数:

在一个字符串中搜索正则表达式的第一个位置,返回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

从一个字符串的开始位置起匹配正则表达式,返回match对象,参数同search函数。举例说明:

import re
match = re.match(r'[1-9]\d{5}', 'BIT 100081')
print(match.group(0))
# 报错,match为空

搜索字符串,以列表类型返回全部能匹配的子串,参数同search。举例说明:

import re
ls=re.findall(r'[1-9]\d{5}', 'BIT100081 TSU100084')
print(ls)
# ['100081', '100084']

将一个字符串按照正则表达式匹配结果进行分割返回列表类型,参数参考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']
import re
for m in re.finditer(r'[1-9]\d{5}', 'BIT100081 TSU100084'):
    if m:
        print(m.group(0))

# 100081
# 100084

在一个字符串中替换所有匹配正则表达式的子串返回替换后的字符串:

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}?:扩展前一个字符mn次(含n),最小匹配

只要长度输出可能不同的,都可以通过在操作符后增加?变成最小匹配