本週 Windows PowerShell 秘訣

Office Space

這是使用 Windows PowerShell 的快速秘訣。只要有新的秘訣,我們每週就會在這裡發佈。如果您想要分享任何秘訣,或有任何疑問,請讓我們知道

您可以在本週 Windows PowerShell 秘訣封存找到更多秘訣。

使用計算屬性

讓 Windows PowerShell 變得這麼有趣的一個原因是:您學會某些很酷 (而且很有用) 的東西才沒多久,就會發現有更酷 (而且更有用) 的方法可以執行相同的工作。

例如,當您首次開始嘗試使用 PowerShell 時,很可能興奮地學習 Get-ChildItem cmdlet,畢竟 Get-ChildItem 可讓您擷取資料夾中所有檔案的資訊,而且只使用單一命令就可以完成:

Get-ChildItem C:\Test

在命令提示字元輸入上述命令 (或從指令碼呼叫),您會獲得類似下面的內容:

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:\Test 資料夾中,每個檔案的 Name、CreationTime 以及 Length (大小)。但是,我們已經得到 3 個中的 2 個資訊,還不賴吧?

錯!可以取回全部 3 個屬性值時,為何只獲得 3 個中的 2 個呢?最可能的是您在街頭巷尾某處學習到 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

這才「真的」酷,而您之前要的所有資料都有了:我們可以看到每個檔案的 Name、CreationTime 以及 Length。這真是個完美的命令和完美的輸出。

好好好:它「幾乎」是完美的命令,而且幾乎是完美的輸出。除了檔案大小以位元組顯示之外,其他部分都還不錯,我們已經習慣查看以 KB 顯示的檔案大小。但是,除了撰寫更複雜的指令碼來遞迴集合中所有的檔案並以 KB 計算大小之外,您打算怎麼辦?

下面是您將要做的事:

Get-ChildItem C:\Test | Select-Object Name, CreationTime,  @{Name="Kbytes";Expression={$_.Length / 1Kb}}

我們在此利用很酷 (但很少人知道) 的 PowerShell 功能:計算屬性。計算屬性可以顧名思義:它是一個物件的屬性,但不是物件所繼承的內建屬性。而是我們自行建立用來執行計算的屬性 (也就是執行指令碼區塊)。現在您可能已經知道,檔案並沒有內建稱為 Kbytes 的屬性,可讓您以 KB 為單位傳回檔案大小。因此,我們開始自行建立這個屬性。下面是我們取回的輸出:

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

不錯喔!但是,建立計算屬性的時候,我們到底做了什麼呢?如您所見,此命令的第一部份相當簡單;我們只是利用 Get-ChildItem,傳回在 C:\Test 資料夾中找到的所有項目的集合:

Get-ChildItem C:\Test

下一個部分也相當簡單:我們將 Get-ChildItem 傳回的資料傳送到 Select-Object cmdlet,然後要求 Select-Object 為我們擷取兩個屬性值 (Name 與 Creation Time)。這塊程式碼就是用來執行這項命令的:

Select-Object Name, CreationTime,

但是,等一下,我們尚未完成工作。我們還要求 Select-Object 擷取「第三個」屬性值,就是我們稱為 Kbytes 的計算屬性:

@{Name="Kbytes";Expression={$_.Length / 1Kb}} 

現在,不要驚慌;這個計算屬性比它首次出現時還要簡單。為了指定計算屬性,我們需要建立雜湊表;也就是 ${} 語法為我們所做的事。在大括弧內部,我們指定雜湊表的兩個元素:屬性 Name (在此情況下是 Kybtes) 與屬性 Expression (也就是我們將用來計算屬性值的指令碼區塊)。Name 屬性很容易指定;只要將字串值指定給 Name,如下所示:

Name="Kbytes"

而不論您是否相信,Expression 屬性 (用分號與名稱隔開) 也不難設定;唯一的差異是 Expression 被指定為指令碼區塊而不是字串值:

Expression={$_.Length / 1Kb}

那個指令碼區塊做了什麼呢?我們想要知道以 KB 為單位的檔案大小;我們可以取得 Length 屬性,然後將它除以數值常數 1Kb。換句話說,這就是我們要執行的計算:

$_.Length / 1Kb

那猜猜看這個指令碼區塊做了什麼呢?答對了:在此情況下,這個指令碼區塊只是將檔案長度轉換成 KB 的命令而已。這樣就完成啦!

讓我們展示更簡單 (但比較沒什麼用途) 的範例。假設您要顯示每個檔案的名稱,而且檔案名稱全部以大寫字母顯示。(我們已經告訴過您,這個範例可能不是很管用。)我們來看一下執行這個不太有用之工作的命令:

Get-ChildItem C:\Test | Select-Object Name, @{Name="UCaseName"; Expression={$_.Name.ToUpper()}}

如您所見,我們要求 Select-Object 送回 Name 屬性,以及我們命名為 UCaseName 的計算屬性。我們再看一下這個計算屬性的 Expression:

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

這真得好多了。

或者這樣真的比較好嗎?在離開之前,我們看一下原始計算屬性最後一眼,也就是看一下判斷以 KB 為單位的檔案大小的計算屬性。您可以回想一下,我們得到的輸出如下:

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 的大小:

320.91796875

對了!我們可以對它做什麼呢?

您已經知道答案了,不是嗎?我們來看一下另一個單行 PowerShell 命令,此命令回報 C:\Test 中每個檔案的 Name 與 Length。不過,這次我們使用 .NET Framework 格式化字串,指定我們的答案需要 0 個小數點位數 ({0:N0}):

Get-ChildItem C:\Test | Select-Object Name, @{Name="Kbytes";Expression={ "{0:N0}" -f ($_.Length / 1Kb) }}

好點子:這現在「是」稍微複雜一點的指令碼區塊了,不是嗎?畢竟它不只計算檔案的 KB 大小,還需要適當地格式化產生的檔案大小。PowerShell 及其計算屬性對工作有幫助嗎?您自己看一下:

Name                   Kbytes
----                   ------
challenge.mdb          884
pool.mdb               224
scores.mdb             260
wordlist.txt           321

我們下週再見。

顯示: