Windows PowerShell每周提示(38):使用已计算的属性
一件让使用Windows PowerShell变得如此有乐趣的事是:当你刚认识到某些事很酷时,随后你就会发现还有其它更酷(及更有用)的方法来完成同样的任务。
例如,当你刚开始尝试使用PowerShell是你可能会为学习Get-ChildItem cmdlet感到兴奋。毕竟,Get-ChildItem使你能够提取一个文件夹内所有文件的信息,而需要做的仅仅是使用一个命令:
在命令提示符中输入以上命令(或者调用一个脚本)你将得到类似以下的输出:
| Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 4/2/2008 9:11 AM 905216 challenge.mdb -a--- 2/12/2008 10:21 AM 229376 pool.mdb -a--- 7/3/2007 2:14 PM 266240 scores.mdb -a--- 1/23/2008 1:39 PM 328620 wordlist.txt |
很酷吧?
是的,确实很酷。当然,那不是你完全想要的信息。你真正想得到的是在文件夹C:\Script中的每个文件的Name,CreationTime及Length(大小)。但是,我们已经得到了两个属性,很不错吧?
错,为什么我们得到了三个属性却只能勉强接受两个属性?也许今后有一天你了解了Select-Object cmdlet,知道该cmdlet能让你指定你想要提取的属性值,甚至是默认情况下没有出现的属性值。(就像CreationTime这个例子,换句话说,当你调用Get-Cmdlet时你将不会看到CreationTime属性的值,即使这是一个文件或文件夹的合理属性。)通过管道将数据传递给Select-Object使得你能选取你想要的属性,甚至可以指定这些属性在输出时的顺序:
| Get-ChildItem C:\Test | Select-Object Name, CreationTime, Length |
通过运行此命令我们将会得到些什么呢?类似以下的内容:
| Name CreationTime Length ---- ------------- ------ challenge.mdb 12/17/2007 9:33:24 PM 905216 pool.mdb 1/14/2008 8:16:15 AM 229376 scores.mdb 1/3/2007 8:00:00 AM 266240 wordlist.txt 1/3/2007 8:00:00 AM 328620 |
现在看上去就很酷了,这是你想要的结果:名称,创建时间,每个文件的大小。这是个完美的命令,输出结果也是完美的。
是的:这几乎是完美的命令和输出结果。这很好,除了文件的大小是以字节的形式呈现,我们习惯于见到以千字节形式呈现的文件大小。但是,我们还可以再写一个更复杂的脚本来遍历集合中的所有文件,并以千字节来计算文件的大小。
以下就是这个更复杂的脚本:
| Get-ChildItem C:\Test | Select-Object Name, CreationTime, @{Name="Kbytes";Expression={$_.Length / 1Kb}} |
我们在这里所做的事是利用一项很酷但是又很少有人知道的PowerShell的特性:已计算的属性。该特性就如其名字所暗示的那样:它是一个对象的属性,但是不是一个对象所继承和内置的属性。而是,通过我们自己执行计算(例如,运行脚本块)所获得属性。就如你目前所知道的那样,文件对象没有一个内置的,以千字节形式返回文件大小的名为Kbytes的属性。因此,我们自行创建了该属性。以下是我们得到的输出结果:
| Name CreationTime Kbytes ---- ------------- ------ challenge.mdb 12/17/2007 9:33:24 PM 884 pool.md 1/14/2008 8:16:15 AM 224 scores.mdb 1/3/2007 8:00:00 AM 260 wordlist.txt 1/3/2007 8:00:00 AM 320.91796875 |
这很棒,但是确切来说我们该如何创建一个已计算的属性呢?如你所见,命令的第一部分很简单。我们只是使用Get-ChildItem来返回一个在C:\Test中找到的所有文件的集合:
下面一个部分也很简单:我们使用管道将Get-ChildItem返回的数据传递给Select-Object cmdlet,并让Select-Object来为我们抓取两个属性,名称及创建时间。也就是下面这部分代码所完成的:
| Select-Object Name, CreationTime, |
但是,我们所做的并不止这些。我们同样让Select-Object抓取第三个属性值,被我们命名为Kbytes的已计算的属性值:
| @{Name="Kbytes";Expression={$_.Length / 1Kb}} |
现在,不要感到惊慌,已出现的属性值比起它首次出现的时候已经简单多了。为了指定一个已计算的属性,我们需要创建一个哈希表,也就是@{}语法所完成的。在花括号内,我们为哈希表指定了两个元素:Name属性(本例中是Kbytes)及Expression属性(这是我们用来计算属性值的脚本块)。Name属性很容易指定,我们只是简单的将一个字符串值赋值给它,像这样:
无论你是否相信,Expression属性(通过使用分号与Name属性分隔开来)不是很难配置,唯一的区别是Expresion使用脚本块对其赋值,而不是一个字符串值:
| Expression={$_.Length / 1Kb} |
那么脚本块里是什么?好的,因为我们想以千字节的形式知道文件的大小,所以我们通过将Length属性除以数字常量1KB来获得。换句话说,下面这行代码就是我们想要执行的计算:
那么猜一下在脚本块里发生了些什么?你明白了:在本例中,我们的脚本块只是包含了将文件大小转换为千字节这个简单的命令。事情就是这样。
接下来让我们向你展示一个更简单的(也许有点没有太大用处)的例子。假设你想显示每个文件的文件名及所有字母以大写形式出现的文件名。(我们告诉过你这个例子也许不是那么有用。)让我们看下能完成这个“没有太大用处”任务的命令:
| Get-ChildItem C:\Test | Select-Object Name, @{Name="UCaseName"; Expression={$_.Name.ToUpper()}} |
如你所见,我们让Select-Object在返回Name属性的同时也返回一个名为UCaseName的已计算的属性。让我们看下这个已计算属性的表达式:
| Expression={$_.Name.ToUpper()} |
再一次,我们所做的只是将一个脚本块赋值给Expression属性。在脚本块内部,我们使用Name属性及ToUpper方法。无需多说,该方法创建了一个全部是大写字母的文件名。当我们执行命令后应当得到以下输出:
| Name UCaseName ---- --------- challenge.mdb CHALLENGE.MDB pool.mdb POOL.MDB scores.mdb SCORES.MDB wordlist.txt WORDLIST.TXT |
顺带说一句,UCaseName确实是一个应用到Get-ChildItem所返回对象的属性。假设我们运行先前的命令,并且将结果储存到名为$a的变量中而不是直接在屏幕上显示:
| $a = (Get-ChildItem C:\Test | Select-Object Name, @{Name="UCaseName"; Expression={$_.Name.ToUpper()}}) |
然后建立一个foreach循环来遍历$a中的每一项。在循环内部,我们打算回显UCaseName属性的值,就是我们刚刚发明的属性:
| foreach ($i in $a) {$i.UCaseName} |
那么你期待的结果是?是的,类似以下的内容:
| CHALLENGE.MDB POOL.MDB SCORES.MDB WORDLIST.TXT |
很漂亮。
下面的命令对你而言更有用。这个命令提取C:\Test中的所有文件然后回显这些文件的以存在时间。为了完成这件事,我们打算使用名为Age的已计算的属性,该属性是由当前的日期和时间减去每个文件创建时的日期和时间而得到,最后的属性值以天的形式呈现。具体命令看上去像这样:
| Get-ChildItem C:\Test | Select-Object Name, @{Name="Age";Expression={ (((Get-Date) - $_.CreationTime).Days) }} |
而结果就像这样:
| Name Age ---- --- challenge.mdb 128 pool.mdb 101 scores.mdb 477 wordlist.txt 477 |
请记住,Age是这个Get-ChildItem实例所返回对象的真实属性。如果你想要按存在时间的长短来查看这些文件的话,那么只需将结果通过管道传递给Sort-Object:
| Get-ChildItem C:\Test | Select-Object Name, @{Name="Age";Expression={ (((Get-Date) - $_.CreationTime).Days) }} | Sort-Object Age |
下面是我们得到的结果:
| Name Age ---- --- pool.mdb 101 challenge.mdb 128 wordlist.txt 477 scores.mdb 477 |
没有比这更好的事了。
是么?在我们走之前,让我们最后再看下以千字节确定文件大小的已计算的属性值。如同你回想起的那样,当时的结果看上去像这样:
| Name CreationTime Kbytes ---- ------------- ------ challenge.mdb 12/17/2007 9:33:24 PM 884 pool.mdb 1/14/2008 8:16:15 AM 224 scores.mdb 1/3/2007 8:00:00 AM 260 wordlist.txt 1/3/2007 8:00:00 AM 320.91796875 |
这也是一个很不错的输出结果,除了一件事。看下Wordlist.txt文件的大小:
讨厌。我们能处理下这个值么?
你已经知道这个问题的答案了。让们看一下另一个PowerShell命令,该命令回显在C:\Test中的每个文件的名称及大小。然而这次我们使用.NET Framework 格式化字符串来指定我们在结果中不需要任何小数:
| Get-ChildItem C:\Test | Select-Object Name, @{Name="Kbytes";Expression={ "{0:N0}" -f ($_.Length / 1Kb) }} |
问得好,现在这个脚本块看上去有点复杂了。毕竟,该脚本块不仅需要以千字节计算文件的大小,同时也需要格式化最终的文件大小。PowerShell和它的已计算的属性值能完成这个任务么?眼见为实:
| Name Kbytes ---- ------ challenge.mdb 884 pool.mdb 224 scores.mdb 260 wordlist.txt 321 |
下周见。
英文原文:
http://www.microsoft.com/technet/scriptcenter/resources/pstips/apr08/pstip0425.mspx