Windows PowerShell每周提示(37):使用正则表达式筛选集合
如果你坐下来制作一张关于过去100年的伟大科技创新的列表的话,这件事将很美好,因为在这张列表的任何位置都不会出现“平庸”的同义词。毫无疑问人们有想当然使用通配符的倾向,同样毫无疑问的是通配符是非常有用,甚至也许是一位救生员。
例如,假设你在一个文件夹中有上百个文件,并且需要得到一张在这个文件夹中的所有.PS1格式的文件列表。你打算把所有文件提取为一个集合然后靠肉眼逐个检查其中的文件,并希望认出一个带有.PS1文件扩展名的文件?当然不,取而代之的是,你能仅仅通过使用以下命令来剔除.PS1文件之外的所有文件:
或者也许你需要一张所有打头字母为q的文件列表?没问题,这里通配符也能帮你完成此事:
“平庸”的通配符?我们认为不!
关于Windows PowerShell的一件酷事是PowerShell与通配符之间合作的程度。Get-ChildItem cmdlet允许你使用通配符是没什么值得惊讶的,毕竟Get-ChildItem同古老的dir命令一样执行许多杂事,比如:
然而,其它大量的cmdlets也支持通配符。这是什么意思?哪些cmdlets支持通配符?老实说我们也不知道,至少不是在脑海中立即浮现。但是我们确实知道你如何确定一个给定cmdlet是否支持使用通配符:查阅帮助文档。例如,假设你想要知道Clear-Content是否支持使用通配符。那么,只需输入Get-Help Clear-Content –full,随后阅读每一个cmdlet参数的解释:
| -path <string[]> Specifies the paths to the items from which content is deleted. Wildcards are permitted. The paths must be paths to items, not to containers. For example, you must specify a path to one more files, not a path to a directory. Wildcards are permitted. This parameter is required, but the parameter name ("-Path") is optional. Required? true Position? 1 Default value N/A - The path must be specified Accept pipeline input? true (ByPropertyName) Accept wildcard characters? True |
啊,看上去-path参数支持通配符。这意味着你能使用类似以下的命令删除一个文件夹内所有.TXT文件的内容:
| Clear-Content C:\Scripts\*.txt |
很酷吧?
当然,通配符也有自身的限制。例如,假设在文件夹C:\Scripts中有大量文件而且其中某些文件的文件名中包含数字(例如,Test_002.txt),让我们进一步猜测这些数字实际上代表了一些特定的含义,如果你能将文件名中包含数字的文件制成一张列表的话那将会相当便利。你能使用通配符提取这张列表么?并不一定。但是,你确实可以使用以下命令来在文件名中包含2的所有文件:
| Get-Childitem C:\Scripts\*2* |
这确实可行,但是这不是特别便利。毕竟,你现在不得不使用其它0-9之间的数字来重复这一过程。还有,如果你仅对在文件名中有数字000至099的文件感兴趣呢?或者,你对数字根本不敢兴趣,但是却对文件名中以L、M、O、P或者Q打头的文件感兴趣?或者……你明白了吧,在这些情况下,使用通配符的意义并不大。
那么就有理由犯愁了么?当然不,毕竟,Windows PowerShell团队考虑了很多,特别是在Where-Object cmdlet上。
正则表达式及Where-Object Cmdlet
今天没有时间来详细讨论正则表达式,如果你对这个话题了解不多那么你可以看下Technet杂志关于这个话题的相关文章。(该文是为编写VBScript的人所写,但是其中的有关正则表达式语法的很多内容也对PowerShell有用。)这也足够说明正则表达式能完成通配符所不能完成的事。例如,制作一张在文件名中包含数字的所有文件的列表?以下命令能帮你完成这件事:
| Get-ChildItem C:\Scripts | Where-Object {$_.Name -match "\d"} |
问得好:我们通过这个命令做了些什么?好的,首先,我们使用Get-ChildItem来返回在文件夹C:\Scripts中所有文件的集合。当然,我们并不想得到C:\Scripts中的所有文件。相反,我们只需要在文件名中包含数字的文件。那就是我们将集合通过管道传递给Where-Object cmdlet的原因,Where-Object将为我们完成筛选工作。
那么Where-Object如何知道哪些文件的文件名中有数字而哪些又没有呢?好的,Where-Object对每一个文件名做了正则表达式搜索。因为使用了-match操作符所以我们知道这里进行了一次正则表达式搜索。(顺便说一句,这是不区分大小写的搜索。如果要进行区分大小写的搜索,也就是大写的A与小写的a将被视为完全不同的字符,那么需要用-cmatch操作符作为替代。)
我们也知道关于Where-Object的其它两件事及它所做的事。首先我们对文件名进行匹配,而文件名则通过引用$_对象(代表管道中的当前对象)及Name属性获得。其次,它查找一个任意形式的数字。我们是怎么知道这一点的呢?因为正则表达式的\d结构代表的含义是“找到0至9之间的任意数字。”
这就是我们如何获知这一点的。
那么它真的有效么?让我们试试然后看下结果:
| Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2/29/2008 9:20 PM 1059 280.txt -a--- 4/3/2008 11:14 AM 1665 bootmgr2.vbs -a--- 3/14/2008 3:31 PM 35962 ctp2.txt -a--- 3/26/2008 8:46 AM 114156 ctp2_cmdlets.txt -a--- 2/12/2008 11:07 AM 253 DebugMe.ps1 -a--- 4/9/2008 10:22 AM 649 fv.ps1 -a--- 2/23/2008 11:42 PM 31350 temptxt3.123 -a--- 2/23/2008 11:42 PM 43560 temptxt4.34 -a--- 4/14/2008 2:24 PM 437 test.ps1 -a--- 2/25/2008 1:24 PM 25276 test2.ps1 -a--- 2/22/2008 1:28 PM 15618 voter55s_results.txt -a--- 2/22/2008 2:02 PM 43560 votes_round2.txt -a--- 2/22/2008 2:02 PM 31260 votes_round43.txt -a--- 2/18/2008 8:18 PM 15408 words2.txt -a--- 2/18/2008 8:18 PM 8880 words3.txt -a--- 2/18/2008 8:18 PM 6624 words4.txt -a--- 2/18/2008 8:18 PM 3440 words5.txt -a--- 2/18/2008 8:18 PM 6368 words6.txt -a--- 2/18/2008 8:18 PM 16624 words7.txt -a--- 2/18/2008 8:18 PM 6496 words8.txt -a--- 2/18/2008 8:18 PM 2336 words9.txt -a--- 4/17/2008 3:00 PM 186 z.ps1 |
看下列表中的每个文件:每个文件名中都应包括一个数字。
对它自己而言这很有用,然而,我们可以做的更精美。如果你仔细看我们第一个命令的输出的话,你会发现,某些时候数字出现在文件的实际名称中,另一些时候数字出现在文件的扩展名中。(有时两个地方又都有数字)。假设我们不想要在文件扩展名中有数字的任意文件,假设我们想要将返回的数据限制为文件的实际名称中包含数字,下面就是一种实现上述要求的方法:
| Get-ChildItem C:\Scripts | Where-Object {$_.Name -match "\d\.[^\d]"} |
我们使用了和第一个命令相似的一些基本的方法。当然唯一的区别是,我们使用了新的正则表达式。在本例中,我们查询了Name属性中包含任一数字(\d)且其后跟随句点(\.)并在此之后没有跟随一个数字。插入符号(^,这里更适合的解释是逻辑非操作符)的意思是“非”。因此语法[^\d]的意思是,“除了数字以外的任何字符。”
是的,这听上去有点怪异,看上去也有点像。但这是正则表达式的典型案例。正因为它是正则表达式的经典案例,所以的确有效:
| Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2/29/2008 9:20 PM 1059 280.txt -a--- 4/3/2008 11:14 AM 1665 bootmgr2.vbs -a--- 3/14/2008 3:31 PM 35962 ctp2.txt -a--- 2/25/2008 1:24 PM 25276 test2.ps1 -a--- 2/22/2008 2:02 PM 43560 votes_round2.txt -a--- 2/22/2008 2:02 PM 31260 votes_round43.txt -a--- 2/18/2008 8:18 PM 15408 words2.txt -a--- 2/18/2008 8:18 PM 8880 words3.txt -a--- 2/18/2008 8:18 PM 6624 words4.txt -a--- 2/18/2008 8:18 PM 3440 words5.txt -a--- 2/18/2008 8:18 PM 6368 words6.txt -a--- 2/18/2008 8:18 PM 16624 words7.txt -a--- 2/18/2008 8:18 PM 6496 words8.txt -a--- 2/18/2008 8:18 PM 2336 words9.txt |
让我们再举另一个关于数字的例子。假设你有一个类似以下文件名的文件夹的:
| Test_001.txt Test_011A.txt Test_037.txt Test_224.txt Test_357.txt Test_661.txt |
比如说你想要提取编号为000至099之间的文件。在这种情况下,你也许会尝试以下命令:
| Get-ChildItem C:\Scripts | Where-Object {$_.Name -match "Test_0[0-9][0-9]\."} |
再一次,我们使用了相同的基本方法:使用Get-ChildItem来提取所有文件,然后使用Where-Object提取出编号为000至099之间的文件。那么我们如何确定哪些文件是在这个范围内的?这事实上很简单:我们只是查找值为Test_0的字符串,并且其后跟随两位0至9([0-9])之间的数字,而最后为一个句点(\.)。这将筛选出类似Test_224.txt的文件,包含Test_0的文件Test_001A.txt将不会出现在我们的最终文件列表中。为什么?因为数字值其后跟随的是字符A而不是句点。事实上,只有两个文件满足条件:
| Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 4/17/2008 3:08 PM 0 Test_001.txt -a--- 4/17/2008 3:08 PM 0 Test_037.txt |
无可否认,我们还可以使用其它方法来书写正则表达式。毋庸置疑的是书写正则表达式时我们还有比较好的方法。但是这些都不是重点,重点只是向你展示如何使用正则表达式来帮助你定位你要要处理的正确的文件集合。
因为我们早先提到过这个情境,让我们再试下最后一个命令,该命令将提取所有文件名以字母L,M,N,O或者P开始的文件:
| Get-ChildItem C:\Scripts | Where-Object {$_.Name -match "^[lmnop]"} |
如你所见这是一个很简单的正则表达式:我们只是让Where-Object根据出现方括号内的任一字符来检查字符串的起始位(这里再次出现了插入符,当在方括号外使用时,插入符的意思是我们想要匹配字符串的起始位)。换句话说,就是任何以字母L,M,N,O或者P开始的文件名。作为命令的运行结果,我们应当得到类似以下的输出:
| Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2/8/2008 8:47 PM 32 lettercase.txt -a--- 2/18/2008 8:13 PM 0 Matches.txt -a--- 3/4/2008 1:21 PM 0 New Text Document.txt -a--- 2/23/2008 10:13 PM 22 numbers.txt -a--- 4/3/2008 5:55 PM 17875 os_info.vbs -a--- 2/22/2008 1:24 PM 33660 output.txt -a--- 1/14/2008 8:16 AM 229376 pool.mdb -a--- 2/9/2008 9:15 PM 724 presidents.txt -a--- 3/31/2008 8:01 AM 981 progress.htm |
不错吧?
顺便说一句,Where-Object不是唯一支持正则表达式的cmdlet。例如,你也同样能在Select-String cmdlet中使用正则表达式,该cmdlet能搜索文本文件的指定值。例如,你按照典型样式来格式化电话号码使得他们看起来像这样:
想要知道类似这样的电话号码是否出现在文件C:\Scripts\Test.txt中?
试下这个命令并看看会发生什么:
| Get-Content C:\Scripts\Test.txt | Select-String "\(\d{3}\)-\d{3}-\d{4}" |
下周见。
英文原文
http://www.microsoft.com/technet/scriptcenter/resources/pstips/apr08/pstip0418.mspx