PowerShell命令与脚本(12)——文本和正则表达式

PowerShell定义文本

使用引号可以定义字符串,如果想让自己定义的字符串原样输出,可以使用单引号。
如果想让自己的定义的字符中的变量被内容替换,表达式被执行可以使用双引号.
 
1、文本中的特殊字符
如果文本放置在一个闭合的双引号中,Powershell解释器会去寻找特殊字符.在这方便主要有两种特殊字符,一个是变量的前缀“$”,一个是反引号“`”位于数字键1左边。
 
处理变量
将变量放在字符串中,输出时变量会被替换成变量本身的值或者内容。如果将表达式放置在字符串中,并且使用的格式如“$(expression)”,表达式也会被执行,并被替换成表达式执行的输出。
 
Powershell转义字符
在其它编程语言中喜欢将反斜杠作为转义字符,但是在Powershell中扮演转义字符角色的不是反斜杠,而是反引号“`”字符串中的反引号,会对紧跟随其后的字符进行特殊处理。例如下面的,在一个字符串中输出双引号,和换行符。
 
其它的转义字符如下表
Powershell转义字符表
转义字符
描述
`n
换行符
`r
回车符
`t
制表符
`a
响铃符
`b
退格符
`’
单引号
`”
双引号
`0
Null
反引号本身
 
2、Here strings 定义多行文本
@“字符串”@格式定义多行文本,尤其是较长的文本,再好不过了
这里要注意开始和结束的标记必须另起一行。
 
3、用户交互
如果要提示用户输入可以使用read-host
只是有一点Read-Host存储的字符串使用的单引号闭合,也就是说不会自动解析变量,不过可以通过ExpandString方法解析,例如:
但是如果想通过Read-Host接受敏感数据,比如密码,可以使用-asSecureString选项,不过这样读取到的数据为SecureString,及为加过密后的数据,当然你可以将密码转换成普通文本.
 
询问用户名和密码
如果你想授权一个用户需要提供用户凭据,可以使用Get-Credential命令,该命令会弹出一个安全对话框,一旦用户输入完毕,就会返回一个Credential对象包含用户名和密码
 

使用特殊文本命令

1. 字符串操作符

1、格式化字符串
格式化操作符 –F 能够将一个字符串格式化为指定格式,左边是包含通配符的字符串,右边是待插入和替换的字符串。
-F 右边的表达式必选放在圆括号中,作为一个整体,先进行计算,然后在格式化。否则可能会解析错误:
可以在-F的左边放置多个字符串通配符,类似.NET中的String.Format方法。-F右边相应的值或表达式也须要使用逗号分隔。
 
所有的基本操作符形式都大同小异,要处理的数据位于操作符的左右两边,然后通过操作符建立连接。例如,你可以使用下面的语句将文本中指定的字符串替换成目标文本:
-replace操作符有三种实现方式,其它文本操作符也类似地有三种实现方式,像-replace-ireplace-creplace,i前缀表示字符串大小写不敏感(insensitive),c前缀表示字符串大小写敏感(case sensitive)。
 
#下面的例子没有完成替换,因为当前大小写敏感:
第三类i前缀,表示大小写不敏感,和没有前缀的命令逻辑一样(PowerShell中默认的字符串比较是不区分大小写的,所以这里保持一致)。
字符串操作符
操作符
描述
示例
*
代表一个字符串
“PsTips.Net” -like “*”
+
合并两个字符串
“Power” + “Shell”
-replace,-ireplace
替换字符串,大小写不敏感
“PsTips.Net” -replace “tip”,”1″
-creplace
替换字符串,大小写敏感
“PsTips.Net” -replace “Tip”,”1″
-eq, -ieq
验证是否相等,大小写不敏感
“Power” -eq “power”
-ceq
验证是否相等,大小写敏感
“Power” -eq “Power”
-like, -ilike
验证字符串包含关系,允许模式匹配,大小写不敏感
“PsTips.Net” -like “p*”
-clike
验证字符串包含关系,允许模式匹配,大小写敏感
“PsTips.Net” – clike “P*”
-notlike,
-inotlike
验证字符串不包含关系,允许模式匹配,大小写不敏感
“PowerShell” -notlike “PS*”
-cnotlike
验证字符串不包含关系,允许模式匹配,大小写敏感
“PowerShell” -cnotlike “PO*”
-match,-imatch
验证模式匹配,大小写不敏感
“PowerShell” -match “P*”
-cmatch
验证模式匹配,大小写敏感
“Hello” -match “[ao]”
-notmatch,
-inotmatch
验证模式不匹配,大小写不敏感
“Hello” -notmatch “[ao]”
-cnotmatch
验证模式不匹配,大小写敏感
“Hello” -cnotmatch “[ao]”
 
2、设置数值格式
格式化操作符 -f 可以将数值插入到字符串,每一个通配符都有统一的结构。
{index[,alignment][:format]}:
  •  Index:
索引编号用来识别把那个值用来替换通配符。例如你可能使用了多个通配符,或者同一个通配符使用了多次,甚至多种格式。此时,索引编号是唯一能够识别那个值将用来替换。另外两个选项Alignment和Format则作为辅助条件。
  • Alignment:
正数和负数,可以指定目标值是否左对齐或者右对齐。还可以支持数值以指定的宽度显示,如果数值的实际宽度大于指定宽度,则忽略指定宽度。如果数值宽度小于指定宽度,剩余的部分会以空白填充,这一选项非常利于制表。
  • Format:
数值可以被格式化成许多不同的类型,下面会预览这些你可能会用到一些格式概要。
 
格式化语句比较特殊,大小写敏感,这和PowerShell中其它语句的使用稍有不同。下面举个例子:
 
符号
类型
调用示例
输入结果
#
数字占位符
“{0:(#).##}” -f $value
(1000000)
%
百分号
“{0:0%}” -f $value
100000000%
,
千分符
“{0:0,0}” -f $value
1,000,000
,.
一千的整数倍
“{0:0,.} ” -f $value
1000
.
小数点
“{0:0.0}” -f $value
1000000.0
0
占位符 0
“{0:00.0000}” -f
$value
1000000.0000
c
货币
“{0:c}” -f $value
¥1,000.00
d
十进制
“{0:d}” -f $value
1000000
e
科学计数法
“{0:e}” -f $value
1.000000e+006
e
指数通配符
“{0:00e+0}” -f $value
10e+5
f
保留小数位
“{0:f}” -f $value
1000000.00
g
常规
“{0:g}” -f $value
1000000
n
千分符
“{0:n}” -f $value
1,000,000.00
x
十六进制
“0x{0:x4}” -f $value
0x4240
使用上面表格中,你可以快速并舒服地格式化数值,例如千分符的使用可以让用户避免去纠结,1后面到底跟了几个0,是10万呢,还是100万。
另外PowerShell还提供了非常丰富的日期格式化选项,相关的格式见下表。
符号
类型
调用示例
输出
d
短日期格式
“{0:d}” –f $value
2013/6/1
D
长日期格式
“{0:D}” –f $value
2013年6月1日
t
短时间格式
“{0:t}” –f $value
“{0:t}” –f $value
T
长时间格式
“{0:T}” –f $value
23:18:50
f
完整日期和时间(短)
“{0:f}” –f $value
2013年6月1日 23:18
F
完整日期和时间(长)
“{0:F}” –f $value
2013年6月1日 23:18:50
g
标准时间 (短)
“{0:g}” –f $value
2013/6/1 23:18
G
标准时间长 (长)
“{0:G}” –f $value
2013/6/1 23:18:50
M
月日格式
“{0:M}” –f $value
6月1日
r
RFC1123 日期格式
“{0:r}” –f $value
Sat, 01 Jun 2013 23:18:50 GMT
s
排序日期格式
“{0:s}” –f $value
2013-06-01T23:18:50
u
通用日期格式
“{0:u}” –f $value
2013-06-01 23:18:50Z
U
通用排序日期 GMT格式
“{0:U}” –f $value
2013年6月1日 15:18:50
Y
年/月格式模式
“{0:Y}” –f $value
2013年6月
自定义日期格式
dd
一个月中天
“{0:dd}” -f $value
01
ddd
星期的缩写
“{0:ddd}” -f $value
周六
dddd
完整星期
“{0:dddd}” -f $value
星期六
gg
纪年法
“{0:gg}” -f $value
公元
hh
小时0-12
“{0:hh}” -f $value
11
HH
小时0-23
“{0:HH}” -f $value
23
mm
分钟
“{0:mm}” -f $value
18
MM
月份
“{0:MM}” -f $value
06
MMM
月份缩写
“{0:MMM}” -f $value
六月
MMMM
完整月份
“{0:MMMM}” -f $value
六月
ss
“{0:ss}” -f $value
55
tt
上午或者下午
“{0:tt}” -f $value
下午
yy
两位数字的年份
“{0:yy}” -f $value
13
yyyy
四位数字的年份
“{0:yyyy}” -f $value
2013
zz
不包含分钟的时区
“{0:zz}” -f $value
+08
zzz
包含分钟的时区
“{0:zzz}” -f $value
+08:00
下面看一个例子:
 
如果你想找出那些类型支持被格式化选项,只须查找.NET中那些类型支持多余的ToString()方法.
[appdomain]::currentdomain.getassemblies() | ForEach-Object {
$_.GetExportedTypes() | Where-Object {! $_.IsSubclassof([System.Enum])}
} | ForEach-Object {
$Methods = $_.getmethods() | Where-Object {$_.name -eq "tostring"} |%{"$_"};
If ($methods -eq "System.String ToString(System.String)") {
$_.fullname
}
}
 
例如,其中的数据类型 ”全局唯一标示符”:
System.Guid
因为你会经常使用到它,它是全球通用的,下面会给你一个简单的例子来创建GUID。
 
扩展:GUID(全局唯一标识符)
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID 的总数达到了2^128(3.4×10^38)个,所以随机生成两个相同GUID的可能性非常小,但并不为0。所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的情况不会发生。
 
3、固定宽度的制表输出
在一个固定宽度和对齐格式中,显示输出多行文本,要求每一列的输出必选具有固定的宽度。格式化操作符可以设置固定宽度输出。
下面的例子通过DIR返回个目录的中的文件列表,然后通过循环输出,文件名和文件大小,因为文件的名字和大小都是不确定的,长度不一样,所以结果拥挤粗糙,可读性差。
下面固定列宽的结果,就显得可读性强了。要设置列宽可以将一个逗号放置在通配符与列宽编号的中间,负数设置右对齐,正数设置左对齐。

2. PowerShell String对象方法

从之前的章节中,我们知道PowerShell将一切存储在对象中,那这些对象中包含了一系列中的称之为方法的指令。默认文本存储在String对象中,它包含了许多非常有用的处理文本的命令。例如,要确定一个文件的扩展名,可以使用LastIndexOf()获取最后一个字符“.”的位置,继续使用Substring()获取扩展名子串。
另外一条途径,使用Split方法,对文件的完整名称进行分割,得到一个字符串数组,取最后一个元素,PowerShell中可以通过索引-1来获取数组中最后一个元素。
下面的表格会给出String对象的所有方法:
函数
描述
示例
CompareTo()
与另一个字符串比较
(“Hello”).CompareTo(“Hello”)
Contains()
是否包含制定子串
(“Hello”).Contains(“ll”)
CopyTo()
拷贝子串至新字符串中
$a = (“HelloWorld”).toCharArray()(“User!”).CopyTo(0,
$a, 6, 5)$a
EndsWith()
是否以制定子串结尾
(“Hello”).EndsWith(“lo”)
Equals()
是否与另一个字符串相同
(“Hello”).Equals($a)
IndexOf()
返回第一次匹配的所索引
(“Hello”).IndexOf(“l”)
IndexOfAny()
返回字符串中任意字符的首次匹配索引
(“Hello”).IndexOfAny(“loe”)
Insert()
在指定位置插入字符串
(“HelloWorld”).Insert(6,”brave “)
GetEnumerator()
枚举字符串中所有字符
(“Hello”).GetEnumerator()
LastIndexOf()
字符的最后匹配位置
(“Hello”).LastIndexOf(“l”)
LastIndexOfAny()
任意字符的最后匹配位置
(“Hello”).LastIndexOfAny(“loe”)
PadLeft()
左边补齐空白是字符串至指定长度
(“Hello”).PadLeft(10)
PadRight()
右边填充空白是字符串至指定长度
(“Hello”).PadRight(10) + “World!”
Remove()
从指定位置开始移除指定长度
(“PsTips”).Remove(2,2)
Replace()
替换指定字符串
(“PsTips”).replace(“Ps”,”PS1″)
Split()
以指定分隔符切割字符串
(“HelloWorld”).Split(“l”)
StartsWith()
是否以指定子串开始
(“HelloWorld”).StartsWith(“He”)
Substring()
从指定位置取指定长度子串
“HelloWorld”).Substring(4,3)
ToCharArray()
转换成字符数组
(“HelloWorld”).toCharArray()
ToLower()
转换成小写
(“HelloWorld”).toLower()
ToLowerInvariant()
以区域规则转换成小写
(“HelloWorld”).ToUpperInvariant()
ToUpper()
转换成大写
(“HelloWorld”).ToUpper()
ToUpperInvariant
()
以区域规则转换成大写
(“HelloWorld”).ToUpperInvariant
()
Trim()
移除字符串前后空格
(” HelloWorld “). Trim()
TrimEnd()
移除字符串结尾的空格
(“HelloWorld “). TrimEnd()
TrimStart()
移除字符串开始的空格
(” HelloWorld”). TrimStart()
Chars()
返回指定位置的字符
(“Hello”).Chars(0)
以Split()为例来分析方法
在之前的章节中,我们已经知道可以通过Get-Member来查看一个对象中包含了那些可以被调用的方法。正好最为一个简单的回顾,来查看Split的定义。
("Pstips.net" | Get-Member Split).definition
Define属性可以获取方法参数定义,但是可读性比较坑爹。我们仍然用上面表格中的Replace方法,将分隔符稍作替换,即可增强可读性。
之前说过反引号,类似高级语言中的转义符反斜杠。
从上面的输出可以发现Split有6种不同的调用方法,而之前可能更多的只使用过一个参数的方法。PowerShell在处理文本时,可能会碰到多个分隔符,而Split方法调用只须一次即可。
中间有空白,咋整,能移除吗,StringSplitOptions轻装上阵:
"https://www.baidu.com".Split(":./",[System.StringSplitOptions]::RemoveEmptyEntries)
之前有一个小算法题,移除字符串中相邻的重复的空格。在不考虑效率的前提下,可以使用Split先分割,分割后再将得到的元素以指定分隔符拼接。但是拼接用到的Join方法,并不属于string对象,而属于String类,也正是下面要讲的。

3. PowerShell String类方法

使用String类命令:
之前已经讨论过,对象方法和类方法的区别了,再回顾一次。
String对象衍生自string类在控制台输入[String]::然后按Tab键会自动智能提示,这些方法就是String类命令。
Get-Member会返回所有string对象的方法,可以通过参数只返回静态方法,也就是string类命令。使用几率最高的自然Format方法,但是因为PowerShell中已经有了大书特书的-F操作符了,Format方法可以秒杀了。但是Join和Contac还是可以聊聊的。
Join()方法曾经在上一部分演示Split()提到过,它可以将一个数组或者列表字符串合以指定分隔符并成一个字符串。例如自定义一个函数,移除多余的白空格。
function RemoveSpace([string]$text){
$Private:array=$text.Split(" ",[System.StringSplitOptions]::RemoveEmptyEntries)
[string]::Join(" ",$array)
}
Concat()将多个字符串拼接成一个字符串。
Concat()工作起来类似字符串操作符“+”,类似而已,总有区别。
区别在于第一个左表达式必选是一个String类型,否则,麻烦来了:
此时可以使用:
或者:
 

简单模式匹配

在验证用户的条目时,模式识别是必要并且常见的任务。例如判断用户的输入的字符串是否是一个合法的网络IP地址,或者电子邮箱。有用并且高效的模式匹配需要一些能代表确切数字和字符的通配符。
许多年前,人们就发明了简单的模式匹配,一直沿用至今。
#列出当前目录中的文本文件
dir *.txt
# 列出系统目录中以‘n’或‘w’打头的文件
dir $env:windir\[nw]*.*
# 列出文件后缀名以‘t’打头,并且后缀名只有三个字符的文件
dir *.t??
# 列出文件中包含一个’e’到’z’之间任意字符的文件
dir *[e-z].*
 
通配符
描述
示例
*
任意个任意字符,(包含零个字符)
Dir *.txt
?
一个任意字符
Dir *.??t
[xyz]
一个包含在指定枚举集合中的字符
Dir [abc]*.*
[x-z]
一个包含在指定区间集合中的字符
Dir *[p-z].*
上面表格中的通配符主要被使用在文件系统中,但是在字符串操作符-like和-notlike 中也可以。例如通过下面的方式可以简单验证IP地址。
$ip = Read-Host "IP address"
If ($ip -like "*.*.*.*") { "valid" } Else { "invalid" }
 
也可以简单验证电子邮件地址。
$email = ".@."
$email -like "*.*@*.*"
然而上面的例子也仅能验证一些低级错误,还不是很确切。例如a.b.c.d不是一个有效的IP地址,但是上面的模式匹配却能通过验证。
 

正则表达式

1. 定义模式

如果你需要更加精确的模式识别需要使用正则表达式。正则表达式提供了更加丰富的通配符。正因为如此,它可以更加详细的描述模式,正则表达式也因此稍显复杂。 使用下面的表格中列出的正则表达式元素,你可以非常精准的描述模式。这些正则表达式元素可以归为三大类。
字符:字符可以代表一个单独的字符,或者一个字符集合构成的字符串。
限定符:允许你在模式中决定字符或者字符串出现的频率。
定位符:允许你决定模式是否是一个独立的单词,或者出现的位置必须在句子的开头还是结尾。
 
正则表达式代表的模式一般由四种不同类型的字符构成。
文字字符:像”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个字符)
 
限定符
上面表格中列出的每个通配符,可以代表一个确定的字符。使用限定符,可以精确地确定字符的出现频率。例如”\d{1,3}”代表一个数字字符出现1到3次。
元素
描述
*
匹配一个元素0次或者多次(最大限度地匹配)
*?
匹配前面的元素零次或者多次(最小限度地匹配)
.*
匹配任意个数的任意字符(包括0个字符)
?
匹配上一个元素0次或者1次(最大限度地匹配)
??
匹配上一个元素0次或者1次(最小限度地匹配)
{n,}
匹配上一个元素至少n次
{n,m}
匹配上一个元素n至m次
{n}
匹配上一个元素n次
+
匹配上一个元素一次或者多次
 
识别IP地址
类似IP地址的模式通过正则表达式来描述比简单的通配符字符会更加精确。通常会使用字符和量词结合,来指定某个具体的字符应当出现,以及出现的频率:
元素
描述
$
在字符串的结尾匹配
\A
在字符串的开始匹配(包含多行文本)
\b
在单词的边界匹配
\B
不在单词的边界匹配
\Z
在字符串的结尾匹配(包含多行文本)
^
在字符串的开始匹配
$parttern="\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
这里模式被描述成4个类似的数字,每个数字以圆句句号分割,每个数字的位数介于1-3。另外在开始和结尾可以包含空格。当这些数字处于0到255之间时,IP的验证还是挺完美的。
但是当某个数字超过255时,则显得无能为力。
 
验证Email格式
如果你想验证用户提供的E-Mail地址是不是一个合法电子邮件格式,可以使用下面的正则表达式:
$parttern = "\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b"
无论什么时候,希望一个表达式以一个单独的“单词”在文本中出现,可以使用分隔符:单词边界(定位符”\b”),这样正则表达式就会知道你感兴趣的是字符串中除去那些白空格(像空格,制表符,换行符)以外的字符。
紧随其后的正则表达式指定的是那些字符可以被允许出现在电子邮件地址中。被允许的字符放在方括号中,由字符区间(例如:A-Z0-9″)和单个字符(例如:”._%+-“)构成。“+”放在方括号后面是一个限定符,意味着前面的字符至少出现一次。当然你可以规定出现更多的字符。
接下来的是“@”,@之后的字符可以和@前面的一样。在电子邮件地址后面必须出现一个圆句点。但是因为圆句点属于特殊字符,所以加了反斜杠转义,让它以普通字符的形式出现在正则表达式中。
在圆句点之后是域标识,它们完成由字母([A-Z])组成,限定符({2,4})紧随其后指定域标识符应当至少由2个字符,至多由4个字符组成。
但是上面的正则表达式仍旧有一些瑕疵:
$parttern = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$"

2. 同时搜索不同的词语

有时搜索的词语比较含糊不清,因为这些词语可能有多种写法。你可以使用限定符“?”来标记这些词语作为可选字符。非常简单,把“?”放在可选字符后面即可。这样“?”前的字符就变成了可选字符,而不是非得出现。
注意,此处的字符“?”并不代表任何字符,因为怕你可能会联想到简单模式匹配里面的“?”。正则表达式中的“?”,只是一个限定符,它代表的是指定字符或者子表达式出现的频率。具体到上面的例子,“u?”就确保了字符“u”在模式中不是必需的。常用的其它限定符,还有“*”(出现0次后者多次)和“+”(至少出现一次)。
 
如果你想标记更多的连续字符作为可选,可以把这些字符放置圆括号中建立子表达式。下面的子表达可以同时识别“Nov”和“November”:
如果你想使用多个可选的搜索词语,可以使用“或”操作符“|”:
如果你想将搜索的词语和固定文本结合在一起,作为可选,仍然可以使用子表达式:
 

3. 大小写敏感

为了和PowerShell的习惯保持一致,操作符-match是大小写不敏感的,如果你想切换至大小写敏感的操作符可以使用“-cmatch”
 
如果你只想在模式的部分片段中使用大小写敏感,仍旧可以使用-match,但是可以在正则表达式中指定部分模式是不是大小写敏感。跟在“(?i)”结构后的字符大小写不敏感,跟在(?-i)结构后面的字符大小写敏感。下面的例子会演示这个区别,模式中的字符字串 “st”因为被(?-i)标记为大小写敏感,所以可以匹配“TEst”,但不能匹配“TEST”。
但是如果你使用的是.NET framework的对象RegEx,它可以自动的在大小写敏感与不敏感之间切换。既可以使用IgnoreCase参数来显示的指定,也可以在模式中使用上面提到的结构来指定。
[regex]::matches("test", "TEST", "IgnoreCase")
 

4. 文本中搜索信息

正则表达式可以识别模式。它们也可以根据确定的模式从文本中过滤出数据,因此正则表达式是用来处理源文本的一款非常优秀的工具。
例如,你想从一封邮件中过滤出一个确切的电子邮件地址,就可以使用我们之前提到过正则表达式。然后就可以在变量$matches找出返回的结果。在你使用-match操作符时,$matches变量会自动被创建,并存储过滤出的结果。$matches是一个哈希表,你既可以输出一个完整的哈希表,也可以使用在中括号中的名称(键值)逐个访问其中的某个元素。
如果文本中有多个电子邮件,上面的方法还会有效吗?非常遗憾,它不会这样做。操作符-match只会匹配一次正则表达式。因此如果你想在源文本中搜索多个出现的模式,你必须切换至RegEx对象,值得一提的是RegEx对象不像-match,Regex对象默认是大小写敏感的,你要想大小写不敏感,可以参考前面的文章。
 

5. 搜索不同的关键字

你可以使用间隔结构“|”来搜索某一组关键字,然后找出那些实际上出现的关键字:
$matches会告诉你那些关键字实际上在字符串上出现了。但是要注意正则表达式中关键字的顺序。是因为第一个匹配到的关键字会被选中决定的。所以接下来的实例,结果感觉不正确:
所以接下来更改关键字的顺序可以让较长的关键字被选中。
或者,你可以更加精确地定制你的正则表达式,记住你实际上搜索的是一个独立的单词。所以在关键字中加入单词边界,让顺序的影响失效。
也确实这样,-match只会搜索第一次匹配,如果你的源文本中可能须要和包含关键字的多次出现,可以重新使用RegEx
上面的例子使用了.NET中的原始正则表达式类。
 

6. 组

一串原始的文本行通常有大量有用信息,你可以使用子表达式来收集数据,可以在之后单独使用。基本的规则是所有想通过模式来搜索的数据应当放在圆括号中,因为变量$matches会将这些子表达式以单独的序列返回。如果文本行首先包含了数据,然后是其它文本,两者之间以制表符分割,你可以如下描述这段模式:
当使用子表达式时,$matches会包含所有搜索模式,数组的第一个元素命名为“0”,子表达式分别位于两个圆括号中,为了使他们更加便于读取理解,你可以分配给每个子表达式它们自己的名子(键),接下来通过它们去调用匹配的结果。给子表达式命名,可以在圆括号中输入type ?。
每个子表达式检索的结果都需要存储空间,如果特定场合中不需要这些结果可以,可以丢弃它们,因为这样可以提高正则表达式匹配的速度。要丢弃结果,可以在子表达式中的第一个语句上加上“?:
 

7. 深入使用子表达式

借住子表达式的帮助,你可以创建出更加惊人和灵活的正则表达式。例如,怎样定义一个网站中HTML标签的模式呢?一个标签通常包含同样的结构:<tagname [parameter]>…</tagname>,这就意味着可以快速定义出一个非常严格的HTML标签模式:
模式以固定的文本”<body “开始,额外的字符以单词为界。接下来跟着右括号”>”,”>”之后则是中的内容,这些内容可以由任意数量的字符(.*?)组成。圆括号中是一个子表达式,会在$matches中返回检索到的中的结果。结尾的部分为固定文本 “”)开始,另外一次以(“”)终结。如果一个正则表达式支持处理任意标签,那它必须能够自动地找出所有的标签,并且在前后两个位置都能使用。怎样完成它呢?像这样:
上面的正则表达式不在包含预定义的固定HTML 标签,却能匹配所有的HTML标签。它是如何办到的呢?因为初始标签被定义成子表达式,该子表达式以字母开始,可以由任意字母或数字组成。
([A-Z][A-Z0-9]*)
在开始匹配到的标签必须在之后也能迭代匹配到,就是要有头也得有尾,善始善终。此处你会发现引入了一个新写法””,“\1”引用的是第一个子表达式。这样就保证了HTML标签开始的和结尾的一致了。
 

8. 贪婪与非贪婪匹配

根据正则表达式的规则,读者可能会怀疑在匹配HTML标签时,使用的事“.*?”而不是简单的“.*”。毕竟“.*”已经可以匹配足够的字符了。“.*”和“.*?”之间的不同并不容易识别。下面通过一个例子来澄清。
假设你要再一个长文件中匹配英文月份,但是月份并不是以同样的方式出现的。有时使用短格式,有时使用长格式。正如接下来看见的一样,正则表达式完成可以做到。因为正则表达式支持子表达式以可选的形式出现。
上面两种情况正则表达式都能识别月份,但是返回的结果却不相同,一个是Feb,一个是February。默认,正则表达式属于“贪婪”模式。在搜索到Feb后会继续贪婪地搜索更多符合模式的的字符。如果可以整个文本会返回。
然后,如果你主要关心的只是规范的月份名称,你可能更喜欢获取缩写的月份名称。这也正是“??”限定符做的,它会将正则表达式转换成“非贪婪”模式,一旦他识别到一个模式,就会立即返回,不再会检查可选的子表达式是否匹配。
到底限定符“??”和之前的例子中的限定符“*?”有什么联系呢?事实上“*?”不是一个独立量词。它会将“贪婪”模式转换成“非贪婪”模式。这就意味着,你可以使用“?”强制将限定符“*”转换成非贪婪模式,尽可能返回短结果。
 

9. 搜索字符片段

举一个例子演示怎样通过正则表达式轻松的搜索字符串片段。下面会匹配位于两个特定单词中的字符串,并且字符的长度介于1到6之间。
 

10. 替换字符串

之前介绍过-replace操作符,你可以能已经知道了怎样替换字符串中的字串。让我们来回顾一下:
但是这种简单的替换不可能永远都是高效的,因此可以尝试使用正则表达式来完成替换工作。下面有一个好玩的例子,用来演示它怎样实用。
也许你会碰到将多个类似的词语替换成同一个词语这样的需求。如果没有正则表达式,需要重复使用replace操作符多次。而每一次replace都会伴随一次遍历,效率明显很低。取而代之,如果使用正则表达式,则非常方便。
你可以在括号中输入任意的词语,多个词语之间用“|”隔开,这样所有的词语都会被指定的字符串替换掉。
 

11. 使用反向引用

最后一个例子在一个字符串中替换了多个指定的关键字。通常效率还是挺高的,但是有时候你可能不想替换所有出现的关键字,而只是想替换出现在特殊上下文中的关键字。这样的情况下,上下文必须定义在模式中。例如,怎样更改正则表达式,让它只替换名字Miller和Meyer.
输出结果看起来有点奇怪,但是确实是和搜索模式匹配的。被替换掉的仅仅是Mr.或者Mrs. Miller和Mr. 或者 Mrs. Meyer。词语”Mr. Werner”没有被替换。遗憾的是结果没道理替换掉整个模式,至少人名应当保留。这可能吗?
此时反向引用应当登场了。在正则表达式中,不论你什么时候使用圆括号,圆括号中的结果都是分开被评估的。你可以在你的“替换串”中使用这些分离出来的结果。第一个子表达式的结果总是”Mr.” 或者 “Mrs.”。第二个子表达式总是返回人名。词语”$1” 和 “$2″在“替换串”中提供了你的子表达式(因此,数字是一串连续的数字;对于补充的子表达式你可以使用”$3″)。
奇怪的是,第一个反向引用似乎并没有工作。当然原因也非常明显: “$1” and “$2″看起来是PowerShell 变量, 但是实际上它们应当是操作符-replace的正则表达式词语。导致此结果的是你把“替换串”放在了双引号中了,PowerShell会将变量替换成具体的值,而这个值一般情况下应当为空。所以要是反向引用在“替换串”中起作用,你必须将“替换串”放置在单引号中,这样让$变成普通字符,这样PowerShell就不会把它识别为自己的变量了,并完成替换功能:
 

12. 在文本中插入字符

“替换串”可以由多行文本中的多个实例组成。例如,在你平时回复一封邮件时,你可能在新邮件中会通过在行首添加 “>” 符号来引用原邮件的中的内容。正则表达式就可以做这样的标记。
然而,要完成它,你可能得稍微了解一点“多行”模式。通常,该模式是关闭的,此时限定符”^”代表文本的开始,”$”代表文本的结束。要让这两个限定符可以代表文本行的开始和文本行的结束,必须使用”(?m)”来开启“多行”模式。只有这样,–replace 才会在每个单独的文本行之间替换模式。在“多行”模式开启后,限定符”^” 和 “\A”,还有”$” and “\Z”会顿时拥有不同的表现。”\A”仍然会标志文本的开始,而”^”则会标志文本行的开始。”\Z”仍然会标志文本的结尾,而”$”则会标志文本行结尾。
 

13. 删除多余的空格

使用正则表达式可以完成一些日常任务,比如一处一个字符串中多余的白空格。模式需要描述一个空格(字符:“\s”)至少出现两次(限定符:“{2,}”)。然后以一个正常的单空格字符替换。
"太多 太多的 空格 怎么才能减少 " -replace "\s{2,}" ," "

14. 搜索和移除重复的单词

怎样才能移除文本中多余的单词。这里,仍旧可以再次使用空格。模式可以这样定义:
"\b(\w+)(\s+\1){1,}\b"
模式会搜索一个单词(以“\b”定位),它由一个单词组成(字符“\w” 和限定符“+”),白空格紧随以后(字符“\s”和限定符“?”)。该模式中,白空格字符和将要被替换的单词必须至少出现一次(至少一次或者更多次,使用限定符“{1,}”)。整个模式会被第一次出现的反向引用给替换掉,也就是位于第一个的单词。

15. 非捕获组

(expression) 是一种简单的子表达式
(?:expression)是一种特殊的子表达式
后者不会将子表达式的匹配结果加入组中
 

文本处理实例

(一)
问题描述:
有如下一段文本文件,开头有许多描述,字符“~”为有用数据的开始标志,要求:求所有电阻值的个数,平均值,总和,最大值,最小值。
这是一份格式较为规则的文本文件报表。
#文件头有一些无用的描述信息
 
~
深度 电阻值 放射性值
10 1.5 2.4
20 0.4 1.9
30 2.5 0.5
40 1.3
50 3.1
先不解释,直接贴脚本:
#加载文件,并过滤空行
$fullText=Get-Content .\a.txt | where { !([string]::IsNullOrWhiteSpace($_))}
#寻找文件头开始标志
$startFlagIndex=-1
For ($i = 0; $i -lt $fullText.Length; $i++)
   {
      if($fullText[$i].Contains("~"))
      {
        $startFlagIndex=$i
        break
      }
 
   }
#去掉文件头
$fullText=$fullText | Select-Object -Skip ($startFlagIndex+1)
 
<#
 #将文件转换成CSV格式,然后再从CSV转换成对象
 #几经周折后,再要深入进行数据处理,将会变得非常方便
#>
$objs=$fullText | foreach{
    $tokens= $_.Split(' ',[StringSplitOptions]::RemoveEmptyEntries)
    '"{0}"' -f [string]::Join('","',$tokens)
} | ConvertFrom-Csv
 
#统计放射性值不为空的对象
Write-Host "统计放射性值为空的对象"
$objs | where { $_.放射性值 -ne $null } | Format-Table -AutoSize
 
#求所有电阻值的个数,平均值,总和,最大值,最小值
Write-Host "求所有电阻值的个数,平均值,总和,最大值,最小值:"
$objs |  Measure-Object -Property 电阻值 -Average -Sum -Maximum -Minimum
 
输出示例:
回过头再看,脚本完全可以优化为一个foreach循环,每行文本只遍历一次。之所以多次一举,是为了演示分析问题的过程。
同样也能得出一个结论,如果可以尽最大可能从数据源拿到CSV文件格式的数据,PowerShell处理起来更方便,一行搞定!
 
(二)
问题描述:
给出一段学生成绩文本文件如下:
李一 93
王二 83
王三 93
李四 60
王五 75
马六 61
孙七 75
刘八 75
要求打印出成绩相同的学生及成绩。
李一 93
王三 93
王五 75
孙七 75
刘八 75
问题分析:
第一遍遍历,先须要一张哈希表保存各个成绩的出现的次数。
第二遍遍历,将成绩出现次数大于2的名单打印。
$scoreTables=@{}
$stus=Get-Content .\ScoresFile.txt |
foreach {
$stu=$_ -split " "
if($scoreTables.ContainsKey($stu[1]))
{
 $scoreTables[$stu[1]]++
}
else {
    $scoreTables[$stu[1]]=1
}
@{ Score=$stu[1];Name=$stu[0] }
}
 
$stus | where {
$scoreTables[$_.Score] -gt 1
} | foreach {"{0} {1}" -f $_.Name,$_.Score }
用group-object实现统计,比较完美,稍作整理,也贴在这里:
Get-Content .\ScoresFile.txt | ForEach-Object {
[PSCustomObject]@{
 Name = $_.split()[0]
 Value = $_.split()[1]
 }
} | Group-Object Value | Where-Object { $_.Count -gt 1 }|
 ForEach-Object { $_.Group | ForEach-Object { "{0} {1}" -f $_.name,$_.value } }
 #为了和源文件格式保持一直,加入格式化
 
(三)
原始文本:”data1″:111,”data2″:22,”data3″:3,”data4″:4444444,”data5″:589
要求:转换成对象

PowerShell命令与脚本(8)——循环 

输出:
 
(四)提取CSV文件中的域名
扩展:CSV
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。
“CSV”并不是一种单一的、定义明确的格式(尽管RFC 4180有一个被通常使用的定义)。因此在实践中,术语“CSV”泛指具有以下特征的任何文件:
  1. 纯文本,使用某个字符集,比如ASCII、Unicode、EBCDIC或GB2312(简体中文环境)等;
  2. 由记录组成(典型的是每行一条记录);
  3. 每条记录被分隔符分隔为字段(典型分隔符有逗号、分号或制表符;有时分隔符可以包括可选的空格);
  4. 每条记录都有同样的字段序列。
在这些常规的约束条件下,存在着许多CSV变体,故CSV文件并不完全互通。然而,这些变异非常小,并且有许多应用程序允许用户预览文件(这是可行的,因为它是纯文本),然后指定分隔符、转义规则等。如果一个特定CSV文件的变异过大,超出了特定接收程序的支持范围,那么可行的做法往往是人工检查并编辑文件,或通过简单的程序来修复问题。因此在实践中,CSV文件还是非常方便的。
 
有一个CSV文件,其中包含了成千上万的URL链接,每个链接都可能是完整路径包含了文件夹,变量等。希望提取出其中的域名以便于进行深度分析。
我的CSV文件只有一列:
"https://www.pstips.net/diff-with-currentculture-and-currentuiculture.html"
"https://www.pstips.net/tag/powershell-v3"
"https://www.pstips.net/powershell-download-files.html"
"http://www.notelee.com/cs0012-the-type-system-object-is-defined-in-an-assembly-that-is-not-referenced.html"
"http://www.notelee.com/scom-create-wmi-perf-rule.html"
"http://www.lonsoon.com/2013/04/94.html"
"http://www.lonsoon.com/2013/05/101.html"
期望的输出URL中所有的域名结果
分析:可以利用Import-csv命令,因为csv文件没有标题,需要临时指定标题。然后利用.NET中的类System.Uri
Import-Csv .\file.csv -Header "link" | foreach { ( [uri]($_.link) ).Host }
再通过Group-Object进行分组去重
 

 

posted @ 2021-06-26 20:15  Ulysses~  阅读(694)  评论(0编辑  收藏  举报