文本三剑客

149 阅读18分钟

正则表达

正则表达式(Regular Expression,在代码中常简写为regex、regexp或RE),又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。许多程序设计语言都支持利用正则表达式进行字符串操作,例如在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成regex,单数有regexp、regex,复数有regexps、regexes、regexen。

最初的正则表达式出现于理论计算机科学的自动控制理论和形式化语言理论中,在这些领域中有对计算(自动控制)的模型和对形式化语言描述与分类的研究。Perl的正则表达式源自于Henry Spencer于1986年1月19日发布的regex,它已经演化成了PCRE(Perl兼容正则表达式,Perl Compatible Regular Expressions,一个由Philip Hazel开发的,为很多现代工具所使用的库。

理论知识

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

  • 空集\varnothing表示集合\varnothing
  • 空串ε\varepsilon表示集合{ε}\{\varepsilon \}
  • 文字字符在Σ\Sigma中的aa表示集合{a}\{a\}

定义了下列运算:

  • 串接RSRS表示集合{αβαR,βS}\{\alpha \beta \mid \alpha \in R,\beta \in S\},例如{ab,c}{d,ef}={abd,abef,cd,cef}\{ab,c\}\{d,ef\}=\{abd,abef,cd,cef\}
  • 选择RSR|S表示RRSS的并集,例如{ab,c}{ab,d,ef}={ab,c,d,ef}\{ab,c\}|\{ab,d,ef\}=\{ab,c,d,ef\}
  • Kleene星号RR^*表示包含ε\varepsilon并且闭合在字符串串接下的RR的最小子集,这是可以通过RR中的零或多个字符串的串接得到所有字符串的集合,例如{ab,c}={ε,ab,c,abab,abc,cab,cc,ababab,}\{ab,c\}^{*}=\{\varepsilon,ab,c,abab,abc,cab,cc,ababab,\cdots \}

很多地方使用对选择使用符号\cup++\vee替代竖线。为了避免括号,假定Kleene星号有最高优先级,接着是串接,接着是并集,如果没有歧义则可以省略括号,例如(ab)c(ab)c可以写为abcabca(b(c))a|(b(c*))可以写为abca|bc*。正则表达式为了避免多余的量词,定义了?和+,例如aaaa*可以被表达为a+a+;(aε)(a|ε)可以被表达为a?a?,有时增加补算子\sim; R\sim R表示在Σ\Sigma ^{*}上但不在RR中的所有字符串的集合,补算子是多余的,因为它可以使用其他算子来表达(尽管计算这种表示的过程是复杂的,而结果可能以指数增大)。这种意义上的正则表达式可以表达正则语言,精确的是可被有限状态自动机接受的语言类,但是在简洁性上有重要区别,某类正则语言只能用大小指数增长的自动机来描述,而要求的正则表达式的长度只线性的增长。

正则表达式对应于乔姆斯基层级的类型-3文法,但通常编程语言或其相关库(例如PCRE)中实现的正则表达式的表达能力是乔姆斯基层级中类型-3文法的超集[来源请求],在另一方面,在正则表达式和不导致这种大小上的爆炸的非确定有限状态自动机(NFA)之间有简单的映射;为此NFA经常被用作正则表达式的替表示式。

我们还要在这种形式化中研究表达力。如下面例子所展示的,不同的正则表达式可以表达同样的语言:这种形式化中存在着冗余。有可能对两个给定正则表达式写一个算法来判定它们所描述的语言是否本质上相等,简约每个表达式到极小确定有限自动机,确定它们是否同构(等价)。这种冗余可以消减到什么程度?我们可以找到仍有完全表达力的正则表达式的有趣的子集吗?Kleene星号和并集明显是需要的,但是我们>或许可以限制它们的使用。这提出了一个令人惊奇的困难问题。因为正则表达式如此简单,没有办法在语法上把它重写成某种规范形式。过去公理化的缺乏导致了星号高度问题。最近Dexter Kozen用克莱尼代数公理化了正则表达式。

语法知识

正则表达式(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为十六进制转义值,十六进制转义值必须为确定的两个数字长,如'\\backslashx41'匹配"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] \ \hline
\b匹配一个单词边界,也就是指单词和空格间的位置,例如'er\b'可以匹配"never"中的'er',但不能匹配"verb"中的'er'
\B匹配非单词边界,'er\B'能匹配"verb"中的'er',但不能匹配"never"中的'er'
\\匹配\\backslash本身字符
\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>可以匹配
(?:pattern)匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用"或"字符(|)来组合一个模式的各个部分是很有用。例如,'industr(?:y|ies)就是一个比'industry|industries'更简略的表达式
(?=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'

常用的正则表达式(可视化页面jex.im/regulex/):

# 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} 
# 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX) ^((\d{3,4}-)|\d{3.4}-)?\d{7,8} 
# 国内电话号码(0511-4405222021-87888822) \d{3}-\d{8}|\d{4}-\d{7} 
# 18位身份证号码(数字、字母x结尾) ^((\d{18})|([0-9x]{18})|([0-9X]{18})) 
# 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间) ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10} 
# 中文字符的正则表达式 [\u4e00-\u9fa5] # 空白行的正则表达式(可以用来删除空白行) \n\s*\r
# HTML标记的正则表达式 <(\S*?)[^>]*>.*?</\1>|<.*?  
# 腾讯QQ号(腾讯QQ号从10000开始) [1-9][0-9]{4,} 
# 中国邮政编码(中国邮政编码为6位数字) [1-9]\d{5}(?!\d)  
# IP地址(提取IP地址时有用) \d+.\d+.\d+.\d+  
# 二进制数是否可以被3整除 ^1((10*1)|(01*0))*10*$

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]+." # 统计文件或者文本中包含匹配字符串的行数-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(/images/index/im2.jpg);" | grep -oP '(?<=url()[^)]+'  # 被3整除的二进制数 ^1((10*1)|(01*0))*10*$    # 使用自动状态机

RE模块

Python自带的re模块主要包含如下6种方法:

  • re.compile: 编译一个正则表达式模式(pattern)
  • re.match: 从头开始匹配, 使用group()方法可以获取第一个匹配值
  • re.search: 用包含方式匹配,使用group()方法可以获取第一个匹配值
  • re.findall: 用包含方式匹配,把所有匹配到的字符放到以列表中的元素返回多个匹配值
  • re.sub: 匹配字符并替换
  • re.split: 以匹配到的字符当做列表分隔符,返回列表

re.compile方法: compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。其函数包含两个参数,一个pattern,一个可选参数flags。re.compile(pattern[, flags]),参数如下:

  • pattern : 一个字符串形式的正则表达式
  • flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:
    • re.I 忽略大小写
    • re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
    • re.M 多行模式
    • re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
    • re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
    • re.X 为了增加可读性,忽略空格和 # 后面的注释

上述flags re.I和re.M是非常常用的。如果要同时使用两个flags,可以使用re.I | re.M。下例中我们编写了一个电子邮箱的正则表达式,并用它来验证用户输入的邮箱是否有效。你可以看到有匹配的对象存在。如果没有匹配到字符串,re.match方法会返回None。因此我们可以用if re.match(pattern, string)来判断来检查是否有匹配。你或许要问了,我们如何从有匹配的字符串中提取匹配到的字符串呢? 我们马上讲到。

>>> import re 
>>> email_pattern = re.compile(r'^[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+){0,4}@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+){0,4}$') 
>>> re.match(email_pattern, 'django@pyghon.org') 
<_sre.SRE_Match object; span=(0, 17), match='django@pyghon.org'>

re.match和re.search方法: re.match和re.search方法类似,唯一不同的是re.match从头匹配,re.search可以从字符串中任一位置匹配。如果有匹配对象match返回,可以使用match.group()提取匹配字符串。re.match(pattern, string)和re.search(pattern, string)。

我们来看个实际案例。下例中我们编写了一个年份的正则表达式, 试图用它从"我爱1998和1999年"中提取年份信息。'\d+{4}'代表一个正整数重复4次,即四位整数。你可以看到re.match没有任何匹配,而re.search也只是匹配到1998年,而没有匹配到1999年。这是为什么呢?re.match是从头匹配的,从头没有符合正则表达式,就返回None。re.search方法虽然可以从字符串任何位置开始搜索匹配,但一旦找到第一个匹配对象,其就停止工作了。如果想从一个字符串中提取所需符合正则表达式模式的所有字符串,你需要使用re.findall方法。

>>> year_pattern = re.compile(r'\d{4}$') 
# 四位整数,匹配年份 
>>> string1 = '我爱1998和1999年' 
>>> match1 = re.match(year_pattern, string1) 
>>> print(match1) None 
>>> match2 = re.search(year_pattern, string1) 
>>> print(match2) <_sre.SRE_Match object; span=(2, 6), match='1998'> 
>>> print(match2.group()) 1998

re.match和re.search方法虽然一次最多只能返回一个匹配对象,但我们可以通过在pattern里加括号构造匹配组返回多个字符串。下例展示了我们如何从"Elephants are bigger than rats"里提取Elephants和bigger两个单词。注意一个括号对于一个group,而match.group的编号是从1开始的,而不是像列表一样从0开始。

re.findall方法: 当您试图从一个字符串中提取所有符合正则表达式的字符串列表时需要使用re.findall方法。findall方法使用方法有两种,一种是pattern.findall(string) ,另一种是re.findall(pattern, string)。re.findall方法经常用于从爬虫爬来的文本中提取有用信息。

# 例1: pattern.findall(string) - 提取年份列表 
>>> year_pattern = re.compile(r'\d{4}$') 
# 四位整数,匹配年份 
>>> string1 = '我爱1998和1999年' year_pattern.findall(string1) ['1998', '1999']  # 例2: re.findall(pattern, string) - 提取百度首页带有链接的关键词 
import requests response = requests.get('https://www.baidu.com') 
urls = re.findall(r'<a.*>(.*)</a>', response.text,) 
# 获取带链接的关键词 for url in urls:     print(url)

re.sub方法: re.sub的使用方法是re.sub(pattern, new_string, current_string)。下例展示了如何把年份替换为****。该方法经常用于去除空格,无关字符或隐藏敏感字符。

>>> year_pattern = re.compile(r'\d{4}$') 
# 四位整数,匹配年份 
>>> string1 = '我爱1998和1999年' 
>>> replaced_str = re.sub(year_pattern, '****', string1) 
>>> print(replaced_str) 
我爱****和****年

re.split方法: re.split的使用方法是re.split(pattern, string),返回分割后的字符串列表。re.split方法并不完美,比如下例中分割后的字符串列表首尾都多了空格,需要手动去除。

>>> string1 = "1cat2dogs3cats4" 
>>> import re >>> list1 = re.split(r'\d+', string1) 
>>> print(list1) ['', 'cat', 'dogs', 'cats', '']

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,然后读取文件,读入有\\backslashn换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$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 ) statementwhile循环语句
for( expression ; expression ; expression ) statementfor循环语句
for( var in array ) statementfor循环语句
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
ERRNOUNIX系统错误消息
FILENAMEawk浏览的文件名
FIELDWIDTHS输入字段宽度的空白分隔字符串
FNR当前文件的浏览数
FS输入字段分隔符,默认是空格,可以使用-F参数指定
IGNORECASE如果为真,则进行忽略大小写的匹配
NF当前记录中的字段个数,就是有多少列,跟-F参数有关
NR已经读出的记录数,就是行号,从1开始
OFMT数字的输出格式,默认为%.6g
OFS输出字段分隔符,默认也是空格
ORS输出记录分隔符,默认为换行符
RLENGTH被匹配函数匹配的字符串长度
RS输入记录的分隔符,默认为换行符
RSTART被匹配函数匹配的字符串首位置
$0当前记录(作为单个变量)
1 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把每一行都存在临时缓冲区中,对这个副本进行编辑,所以不会修改原文件。

image.png

一般情况下,sed读取一行处理一行输出一行,清空模式空间,然后重复这个步骤直到文件结束,由于某些特殊情况下用户希望模式空间可以保留到下一次处理,也就是使得sed可以对多行处理之后在输出处理结果,所以在这种需求下,就需要涉及到模式空间、暂存空间两个缓存区之间的数据交换,暂存空间相当于一个临时的仓库,在sed进行数据处理的时候,可以作为数据的暂存区域,暂存区域默认有一个换行符存在。

image.png

定址: 定址用于决定对哪些行进行编辑。地址的形式可以是数字、正则表达式、或二者的结合,如果没有指定地址,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\用此符号后的新文本替换当前行中的文本。多行时除最后一行外,每行末尾需用"\\backslash"续行
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 ... 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/'  
# 逆序show文件 
sed  -r '1!G;$!h;$!d'  file