PowerShell笔记-13.正则表达式
本系列是⼀个重新学习PowerShell的笔记,内容引⽤⾃
定义模式
如果你需要更加精确的模式识别需要使⽤正则表达式,正则表达式提供了更加丰富的通配符。正因为如此,它可以更加详细的描述模式,正则表达式也因此稍显复杂。使⽤下⾯
的表格中列出的正则表达式元素,你可以⾮常精准的描述模式。
这些正则表达式元素可以归为三⼤类。
字符:字符可以代表⼀个单独的字符,或者⼀个字符集合构成的字符串。
限定符:允许你在模式中决定字符或者字符串出现的频率。
定位符:允许你决定模式是否是⼀个独⽴的单词,或者出现的位置必须在句⼦的开头还是结尾。
正则表达式代表的模式⼀般由四种不同类型的字符构成。
⽂字字符:像”abc”确切地匹配”abc“字符串
转义字符:⼀些特殊的字符例如反斜杠,中括号,⼩括号在正则表达式中居于特殊的意义,所以如果要专门识别这些特殊字符需要转义字符反斜杠。就像”[abc]”可以识别”[abc]”。
预定义字符:这类字符类似占位符可以识别某⼀类字符。例如”\d”可以识别0-9的数字。
⾃定义通配符:包含在中括号中的通配符。例如”[a-d]”识别a,b,c,d之间的任意字符,如果要排除这些字符,可以使⽤”[^a-d]”。
字符
元素描述
.匹配除了换⾏符意外的任意字符
[^abc]匹配除了包含在中括号的任意字符
[^a-z]匹配除了包含在中括号指定区间字符的任意字符
[abc]匹配括号中指定的任意⼀个字符
[a-z]匹配括号中指定的任意区间中的任意⼀个字符
\a响铃字符(ASCII 7)
\c or \C匹配ASCII 中的控制字符,例如Ctrl+C
\d匹配数字字符,等同于[0-9]
\D匹配数字以外的字符
\e Esc (ASCII 9)
\f换页符(ASCII 15)
\n换⾏符
\r回车符
\s⽩空格(空格,制表符,新⾏)
\S匹配⽩空格(空格,制表符,新⾏)意外的字符
\t制表符
\uFFFF匹配Unicode字符的⼗六进制代码FFFF。例如,欧元符号的代码20AC
\v匹配纵向制表符(ASCII 11)
\w匹配字符,数字和下划线
\W匹配字符,数字和下划线意外的字符
\xnn匹配特殊字符,nn代表⼗六进制的ASCII 码
.*匹配任意数量的字符(包括0个字符)
限定符
元素描述
*匹配⼀个元素0次或者多次(最⼤限度地匹配)
.*匹配任意个数的任意字符(包括0个字符)
?匹配上⼀个元素0次或者1次(最⼤限度地匹配)
?
?匹配上⼀个元素0次或者1次(最⼩限度地匹配)
{n,}匹配上⼀个元素⾄少n次
{n,m}匹配上⼀个元素n⾄m次
{n}匹配上⼀个元素n次
+匹配上⼀个元素⼀次或者多次
定位符
元素描述
$在字符串的结尾匹配
\A在字符串的开始匹配(包含多⾏⽂本)
\b在单词的边界匹配
\B不在单词的边界匹配
\Z在字符串的结尾匹配(包含多⾏⽂本)
^在字符串的开始匹配
简单案例
识别IP
类似IP地址的模式通过正则表达式来描述⽐简单的通配符字符会更加精确。通常会使⽤字符和量词结合,来指定某个具体的字符应当出现,以及出现的频率:
PS C:\PowerShell> $parttern = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b" PS C:\PowerShell> "192.168.10.3" -match $parttern True
PS C:\PowerShell> "a.168.10.3" -match $parttern False
PS C:\PowerShell> "1000.168.10.3" -match $parttern False
这⾥模式被描述成4个类似的数字,每个数字以圆句句号分割,每个数字的位数介于1-3。另外在开始和结尾可以包含空格。当这些数字处于0到255之间时,IP的验证还是挺完美的。
但是当某个数字超过255时,则显得⽆能为⼒。
PS C:\PowerShell> "255.489.921.321" -match $parttern True
验证Email格式
如果你想验证⽤户提供的E-Mail地址是不是⼀个合法电⼦邮件格式,可以使⽤下⾯的正则表达式:
PS C:\PowerShell> $parttern = "\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b" PS C:\PowerShell> "mosser@pstips" -match $parttern True PS C:\PowerShell> ".@ ." -match $parttern False
⽆论什么时候,希望⼀个表达式以⼀个单独的“单词”在⽂本中出现,可以使⽤分隔符:单词边界(定位符”\b”),这样正则表达式就会知道你感兴趣的是字符串中除去那些⽩空格(像
空格,制表符,换⾏符)以外的字符。
紧随其后的正则表达式指定的是那些字符可以被允许出现在电⼦邮件地址中。被允许的字符放在⽅括号中,由字符区间(例如:A-Z0-9)和单个字符(例如:”._%+-“)构成。“+”放
在⽅括号后⾯是⼀个限定符,意味着前⾯的字符⾄少出现⼀次。当然你可以规定出现更多的字符。
接下来的是“@”,@之后的字符可以和@前⾯的⼀样。在电⼦邮件地址后⾯必须出现⼀个圆句点。但是因为圆句点属于特殊字符,所以加了反斜杠转义\.,让它以普通字符的形式
出现在正则表达式中。
在圆句点之后是域标识,它们完成由字母([A-Z])组成,限定符({2,4})紧随其后指定域标识符应当⾄少由2个字符,⾄多由4个字符组成。
但是上⾯的正则表达式仍旧有⼀些瑕疵:
PS C:\PowerShell> $parttern = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$"
PS C:\PowerShell> "请邮件联系: mosser@pstips 好不好?" -match $parttern False
PS C:\PowerShell> "mosser@pstips" -match $partter True
同时搜索不同的词语
有时搜索的词语⽐较含糊不清,因为这些词语可能有多种写法。你可以使⽤限定符“?”来标记这些词语作为可选字符。⾮常简单,把“?”放在可选字符后⾯即可。这样“?”前的字符就
变成了可选字符,⽽不是⾮得出现。
"color" -match "colou?r"
True
"colour" -match "colou?r"
True
注意,此处的字符“?”并不代表任何字符,因为怕你可能会联想到简单模式匹配⾥⾯的“?”。正则表达式中的“?”,只是⼀个限定符,它代表的是指定字符或者⼦表达式出现的频率。具
体到上⾯的例⼦,“u?”就确保了字符“u”在模式中不是必需的。常⽤的其它限定符,还有“*”(出现0次后者多次)和“+”(⾄少出现⼀次)。
如果你想标记更多的连续字符作为可选,可以把这些字符放置圆括号中建⽴⼦表达式。下⾯的⼦表达可以同时识别“Nov”和“November”:
"Nov" -match "\bNov(ember)?\b"
True
"November" -match "\bNov(ember)?\b"
True
如果你想使⽤多个可选的搜索词语,可以使⽤“或”操作符“|”:
"Bob and Ted" -match "Alice|Bob"
True
如果你想将搜索的词语和固定⽂本结合在⼀起,作为可选,仍然可以使⽤⼦表达式:
# 搜索 "and Bob":
"Peter and Bob" -match "and (Bob|Willy)"
True
# 没有搜索到 "and Bob":
"Bob and Peter" -match "and (Bob|Willy)"
False
⼤⼩写敏感
为了和PowerShell的习惯保持⼀致,操作符-match是⼤⼩写不敏感的,如果你想切换⾄⼤⼩写敏感的操作符可以使⽤“-cmatch”
# -match ⼤⼩写不敏感:
"hello" -match "heLLO"
True
# -cmatch ⼤⼩写敏感:
"hello" -cmatch "heLLO"
False
在⽂本中搜索信息
正则表达式可以识别模式。它们也可以根据确定的模式从⽂本中过滤出数据,因此正则表达式是⽤来处理源⽂本的⼀款⾮常优秀的⼯具。
例如,你想从⼀封邮件中过滤出⼀个确切的电⼦邮件地址,就可以使⽤我们之前提到过正则表达式。然后就可以在变量
matches出返回的结果。在你使⽤‘−match‘操作符时,‘matches变量会⾃动被创建,并存储过滤出的结果。$matches`是⼀个哈希表,你既可以输出⼀个完整的哈
希表,也可以使⽤在中括号中的名称(键值)逐个访问其中的某个元素。
如果⽂本中有多个电⼦邮件,上⾯的⽅法还会有效吗?⾮常遗憾,它不会这样做。操作符-match只会匹配⼀次正则表达式。因此如果你想在源⽂本中搜索多个出现的模式,你必
须切换⾄RegEx对象,值得⼀提的是RegEx对象不像-match,Regex对象默认是⼤⼩写敏感的,你要想⼤⼩写不敏感,可以参考前⾯的⽂章。
#-match 只能匹配⼀个邮箱
PS C:\PowerShell> $rawtext = "test@pstips sent an e-mail that was forwarded to admin@pstips." PS C:\PowerShell> $rawtext -match "\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b" True
PS C:\PowerShell> $matches
Name Value
---- -----
0 test@pstips
# regex 对象可以匹配所有出现的电⼦邮箱地址,默认⼤⼩写敏感
#(?i) 不匹配⼤⼩写 (?-i) 匹配⼤⼩写
PS C:\PowerShell> $regex = [regex]"(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b" PS C:\PowerShell> $regex.Matches($rawtext)
Groups : {0}
Success : True
Name : 0
Captures : {0}
Index : 0
Length : 15
Value : test@pstips
Groups : {0}
Success : True
Name : 0
Captures : {0}
Index : 53
Length : 16
Value : admin@pstips
PS C:\PowerShell> $regex = [regex]"(?-i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b" PS C:\Pow
erShell> $regex.Matches($rawtext) PS C:\PowerShell> $regex = [rege Groups : {0}
Success : True
Name : 0
Captures : {0}
Index : 0
Length : 15
Value : test@pstips
Groups : {0}
Success : True
Name : 0
Captures : {0}
Index : 53
Length : 16
Value : admin@pstips
# 指定输出结果
PS C:\PowerShell> $regex.Matches($rawtext) | Select-Object -Property Value
Value
-----
test@pstips
admin@pstips
PS C:\PowerShell> $regex.Matches($rawtext) | ForEach-Object { "found: $($_.Value)" } found: test@pstips
found: admin@pstips
组
⼀串原始的⽂本⾏通常有⼤量有⽤信息,你可以使⽤⼦表达式来收集数据,可以在之后单独使⽤。基本的规则是所有想通过模式来搜索的数据应当放在圆括号中,因为变
量$matches会将这些⼦表达式以单独的序列返回。如果⽂本⾏⾸先包含了数据,然后是其它⽂本,两者之间以制表符分割,你可以如下描述这段模式:
"定义模式: 由Tab分割的两个字符串"
$pattern = "(.*)\t(.*)"
# ⽣成⼀个由Tab分割的字符串
$line = "12/01/2009`tDescription"
# 使⽤正则表达式匹配:
$line -match $pattern
"显⽰结果:"
$matches
$matches[1]
$matches[2]
PS C:\PowerShell> .\test.ps1 定义模式: 由Tab分割的两个字符串
True
显⽰结果:
Name Value
---- -----
2 Description
1 12/01/2009
0 12/01/2009 Description
12/01/2009
Description
当使⽤⼦表达式时,$matches会包含所有搜索模式,数组的第⼀个元素命名为“0”,⼦表达式分别位于两个圆括号中,为了使他们更加便于读取理解,你可以分配给每个⼦表达式它们⾃⼰的名⼦(键),接下来通过它们去调⽤匹配的结果。给⼦表达式命名,可以在圆括号中输⼊type ?。
# 给⼦表达式命名:
$pattern = "(?<Date>.*)\t(?<Text>.*)"
# ⽣成包含Tab键的字符串
$line = "12/01/2009`tDescription"
# 使⽤正则表达式匹配:
$line -match $pattern
$matches
$matches.Date
$matches.Text
PS C:\PowerShell> .\test.ps1 True
Name Value
---- -----
Date 12/01/2009
Text Description
0 12/01/2009 Description
12/01/2009
Description
每个⼦表达式检索的结果都需要存储空间,如果特定场合中不需要这些结果可以,可以丢弃它们,因为这样可以提⾼正则表达式匹配的速度。要丢弃结果,可以在⼦表达式中的第⼀个语句上加上“?:”
# 第⼆个⼦表达式不返回结果:
$pattern = "(?<Date>.*)\t(?:.*)"
# ⽣成包含制表符的字符串:
$line = "12/01/2009`tDescription"
# 使⽤正则表达式匹配:
$line -match $pattern
# 第⼆个⼦表达式没有多余的结果返回
$matches
PS C:\PowerShell> .\test.ps1 True
Name Value
---- -----
Date 12/01/2009
0 12/01/2009 Description
深⼊使⽤⼦表达式
借住⼦表达式的帮助,你可以创建出更加惊⼈和灵活的正则表达式。例如,怎样定义⼀个⽹站中HTML标签的模式呢?⼀个标签通常包含同样的结构:<tagname [parameter]>…
</tagname>,这就意味着可以快速定义出⼀个⾮常严格的HTML标签模式:
PS C:\PowerShell> "<body background=1>www.pstips</body>" -match "<body\b[^>]*>(.*?)</body>" True
PS C:\PowerShell> $Matches
Name Value
---- -----
1 www.pstips
0 <body background=1>www.pstips</body>
模式以固定的⽂本<body开始,额外的字符以单词为界。接下来跟着右括号>,之后则是中的内容,这些内容可以由任意数量的字符(.*?)组成。圆括号中是⼀个⼦表达式,会
在$matches中返回检索得到的结果。结尾的部分为固定⽂本<;开始,另外⼀次以/body>终结。
如果⼀个正则表达式⽀持处理任意标签,那它必须能够⾃动地出所有的标签,并且在前后两个位置都能使⽤。怎样完成它呢?像这样:
PS C:\PowerShell> "<body background=2>Contents</body>" -match "<([A-Z][A-Z0-9]*)[^>]*>(.*?)</\1>" True
PS C:\PowerShell> $Matches
Name Value
---- -----
2 Contents
正则匹配哈希值
1 body
0 <body background=2>Contents</body>
上⾯的正则表达式不在包含预定义的固定HTMl 标签,却能匹配所有的HTML标签。它是如何办到的呢?因为初始标签被定义成⼦表达式,该⼦表达式以字母开始,可以由任意字母或数字组成。
([A-Z][A-Z0-9]*)
在开始匹配到的标签必须在之后也能迭代匹配到,就是要有头也得有尾,善始善终。此处你会发现引⼊了⼀个新写法””,“\1”引⽤的是第⼀个⼦表达式。这样就保证了HTMl标签开始的和结尾的⼀致了。
PS C:\PowerShell> $regexTag = [regex]"(?i)<([A-Z][A-Z0-9]*)[^>]*>(.*?)</\1>" PS C:\PowerShell> $result = $regexTag.Matches("<button>Press here</button>") PS C:\PowerS
PS C:\PowerShell> $result
Groups : {0, 1, 2}
Success : True
Name : 0
Captures : {0}
Index : 0
Length : 27
Value : <button>Press here</button>
PS C:\PowerShell> $result.Count 1
PS C:\PowerShell> $result[0].Groups | ForEach-Object {$_.Value} <button>Press here</button>
button
Press here
贪婪与⾮贪婪匹配
根据正则表达式的规则,读者可能会怀疑在上⾯匹配HTML标签时,使⽤的是.*?⽽不是简单的.*。毕竟.*已经可以匹配⾜够的字符了。.*和.*?之间的不同并不容易识别。下⾯通过⼀
个例⼦来澄清。
假设你要再⼀个长⽂件中匹配英⽂⽉份,但是⽉份并不是以同样的⽅式出现的。有时使⽤短格式,有时使⽤长格式。正如接下来看见的⼀样,正则表达式完成可以做到。因为正
则表达式⽀持⼦表达式以可选的形式出现。
"Feb" -match "Feb(ruary)?"
#True
$matches[0]
#Feb
"February" -match "Feb(ruary)?"
#True
$matches[0]
PS C:\PowerShell> .\test.ps1
True
Feb
True
February
上⾯两种情况正则表达式都能识别⽉份,但是返回的结果却不相同,⼀个是Feb,⼀个是February。默认,正则表达式属于“贪婪”模式。在搜索到Feb后会继续贪婪地搜索更多符
合模式的的字符。如果可以整个⽂本会返回。
然后,如果你主要关⼼的是规范的⽉份名称,你可能更喜欢获取缩写的⽉份名称。这也正是??限定符做的,它会将正则表达式转换成“⾮贪婪”模式,⼀旦他识别到⼀个模式,就会
⽴即返回,不再会检查可选的⼦表达式是否匹配。
"Feb" -match "Feb(ruary)??"
#True
$matches[0]
#Feb
"February" -match "Feb(ruary)??"
#True
$matches[0]
PS C:\PowerShell> .\test.ps1 True
Feb
True
Feb
到底限定符??和之前的例⼦中的限定符*?有什么联系呢?事实上*?不是⼀个独⽴量词。它会将“贪婪”模式转换成“⾮贪婪”模式。这就意味着,你可以使⽤?强制将限定符*转换成⾮
贪婪模式,尽可能返回短结果。这也正是之前在匹配HTML标签时所做的。接下来你会看到假如没有“⾮贪婪”模式,正则表达式会尽可能检索更多的内容,也⾃然会出错。
PS C:\PowerShell> $regexTag = [regex]"(?i)<([A-Z][A-Z0-9]*)[^>]*>(.*)" PS C:\PowerShell> $result = $regexTag.Matches("<button>Press here") PS C:\PowerShell> $result[0]. button
Press here
PS C:\PowerShell> $regexTag = [regex]"(?i)<([A-Z][A-Z0-9]*)[^>]*>(.*?)"
PS C:\PowerShell> $result = $regexTag.Matches("<button>Press here") PS C:\PowerShell> $result[0].Groups | ForEach-Object { $_.Value } button
已经有专门的书籍来讲解正则表达式,所以深⼊讲解正则表达式超出了⽂本的主题。但是在这⾥还会举⼀个例⼦演⽰怎样通过正则表达式轻松的搜索字符串⽚段。下⾯的的脚本
会匹配位于两个特定单词中的字串,并且字符的长度介于1到6之间。
PS C:\PowerShell> "Find word segments from start to end" -match "\bstart\W+(?:\w+\W+){1,6}?end\b" True
PS C:\PowerShell> $Matches
Name Value
---- -----
0 start to end
替换字符串
之前介绍过-replace操作符,你可以能已经知道了怎样替换字符串中的字串。让我们来回顾⼀下:
PS C:\PowerShell> "Hello, PowerShell" -replace "PowerShell", "www.pstips" Hello, www.pstips
但是这种简单的替换不可能永远都是⾼效的,因此可以尝试使⽤正则表达式来完成替换⼯作。
下⾯有⼀个好玩的例⼦,⽤来演⽰它怎样实⽤。
也许你会碰到将多个类似的词语替换成同⼀个词语这样的需求。如果没有正则表达式,需要重复使⽤replace操作符多次。⽽每⼀次replace都会伴随⼀次遍历,效率明显很低。
取⽽代之,如果使⽤正则表达式,则⾮常⽅便。
PS C:\PowerShell> "Mr. Miller and Mrs. Meyer" -replace "(Mr.|Mrs.)", "Our client" Our client Miller and Our client. Meyer
你可以在括号中输⼊任意的词语,多个词语之间⽤“|”隔开,这样所有的词语都会被指定的字符串替换掉。
在⽂本⾏的开始插⼊字符
“替换串”可以由多⾏⽂本中的多个实例组成。例如,在你平时回复⼀封邮件时,你可能在新邮件中会通过在⾏⾸添加 “>” 符号来引⽤原邮件的中的内容。正则表达式就可以做这样
的标记。
然⽽,要完成它,你可能得稍微了解⼀点“多⾏”模式。通常,该模式是关闭的,此时限定符”^”代表⽂本的开始,”
”代表⽂本的结束。要让这两个限定符可以代表⽂本⾏的开始和⽂本⾏的结束,必须使⽤”(?m)”来开启“多⾏”模式。只有这样,–replac
” and “\Z”会顿时拥有不同的表现。”\A”仍然会标志⽂本的开始,⽽”^”则会标志⽂本⾏的开始。”\Z”仍然会标志⽂本的结尾,⽽”$”则会标志⽂本⾏结尾。
$text = @"
这是⼀段⽂本,
我想在回复的邮件中引⽤它,
所以我在每⾏的开始追加了">" 符号。
"@
这是⼀段⽂本,
我想在回复的邮件中引⽤它,
所以我在每⾏的开始追加了">" 符号。
# 通常, -replace 没有⼯作在多⾏模式.
# 鉴于此,只有第⼀⾏的开始被替换了:
$text -replace "^", "> "
> 这是⼀段⽂本,
我想在回复的邮件中引⽤它,
所以我在每⾏的开始追加了">" 符号。
# 如果你开启了多⾏模式, 替换串则会多⾏⽂本中起作⽤:
$text -replace "(?m)^", "> "
> 这是⼀段⽂本,
> 我想在回复的邮件中引⽤它,
> 所以我在每⾏的开始追加了">" 符号。
# 你也可以使⽤RegEx对象来完成多⾏替换,
# 不过得显式指定多⾏模式
[regex]::Replace($text, "^", "> ", `
[Text.RegularExpressions.RegExOptions]::Multiline)
> 这是⼀段⽂本,
> 我想在回复的邮件中引⽤它,
> 所以我在每⾏的开始追加了">" 符号。
# 在多⾏模式中 \A 仍旧代表⽂本的开始,^代表⾏的开始
# 这就是为什么下⾯的⽅法只能替换第⼀⾏的开始
[regex]::Replace($text, "\A", "> ", `
[Text.RegularExpressions.RegExOptions]::Multiline)
> 这是⼀段⽂本,
我想在回复的邮件中引⽤它,
所以我在每⾏的开始追加了">" 符号。
删除多余的空格、搜索和移除重复的单词
使⽤正则表达式可以完成⼀些⽇常任务,⽐如⼀处⼀个字符串中多余的⽩空格。模式需要描述⼀个空格(字符:“\s”)⾄少出现两次(限定符:“{2,}”)。然后以⼀个正常的单空格字符替换。
PS C:\PowerShell> "太多太多的空格怎么才能减少 " -replace "\s{2,}" ," " 太多太多的空格怎么才能减少
怎样才能移除⽂本中多余的单词。这⾥,仍旧可以再次使⽤空格。模式可以这样定义:
\b(\w+)(\s+\1){1,}\b
模式会搜索⼀个单词(以“\b”定位),它由⼀个单词组成(字符“\w” 和限定符“+”),⽩空格紧随以后(字符“\s”和限定符“+”)。该模式中,⽩空格字符和将要被替换的单词必须⾄少出现⼀次(⾄少⼀次或者更多次,使⽤限定符“{1,}”)。整个模式会被第⼀次出现的反向引⽤给替换掉,也就是位于第⼀个的单词。
PS C:\PowerShell> "太多太多的话我还没有说,太多太多太多的理由值得你留下" -replace "\b(\w+)(\s+\1){1,}\b", '$1' 太多的话我还没有说,太多的理由值得你留下
Processing math: 100%
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论