引子

  公司在Windows環境下進行開發,所以在寫自動構建的時候,自然而然地想到了CMD SHELL。本來考慮過使用Windows Script Host指令碼(WSF、JS或VBS)來寫,但要在WSH指令碼里呼叫VS的批處理來設定環境很困難。隨著專案結構變得複雜,CMD SHELL寫的構建指令碼也開始變得複雜,這個時候就感到CMD SHELL有點吃力了,於是想到了Powershell。

  在這之前對Powershell其實有過一些瞭解,知道它是一個比CMD SHELL更適合寫指令碼程式的東西,跟.NET有著莫大的關係。不過之前的瞭解也僅此而已,所以還是需要先學習一下。通過Google找到了Tobias Weltner博士寫的《Master Powershell》(http://powershell.com/cs/blogs/ebook/),花了半天揀想了解的部分通看了一遍,於是有了寫下《Powershell學習筆記》的想法。

  感謝Tobias Weltner博士,以及他那本免費的《Master Powershell》!

 

從CMD SHELL到Powershell

  看了《Master Powershell》一遍之後,我覺得學習Powershell是正確的。

  CMD SHELL(以下簡稱CMD)來源於DOS時代的批處理指令碼,它最初的設計就是為著順序批處理來的。在Windows 2000和Windows XP時代,DOS批處理正式升級為CMD SHELL,在語法和功能上做了一些擴充套件;指令碼檔案開始支援.CMD副檔名,併相容之前的.BAT副檔名;其名稱也由“DOS視窗”改為“命令提示符”。CMD SHELL雖然可以完成很多比較複雜的任務,但卻非常考驗指令碼開發者大腦的堆疊大小。

  CMD的確適合寫一些簡單的SHELL程式,而Powershell卻包含了寫一個複雜的指令碼程式所需要的各種支援,包括語法和函式庫——巨大的.NET函式庫。

1. 引數處理,Powershell方便不只一點點

  CMD中處理引數,通常是按順序處理%1-%9,引數比較多還需要通過SHIFT命令來對引數進行移位。如果想處理位置不定的switch引數,或者處理命名引數,那就分析引數這部分指令碼都能把人搞攪暈。而Powershell原生就支援引數陣列、命名引數和switch引數。比如

  1. # test.ps1 
  2. # 在指令碼中定義命名引數和switch引數 
  3. param($name, [switch] $isMale) 
  4. # 如果 . est.ps1 -name "James Fancy" -ismale 
  5. #  $name的值為James Fancy 
  6. #  $isMale的值為True 
  7. # 如果預設-ismale引數,$isMale的值為False 
  8. # 如果 . est.ps1 -name -ismale 
  9. #  會直接報告錯誤,因為-name需要附加的引數 

  可見Powershell已經將引數處理部分進行了很好的封閉。如果要用CMD來寫,那就需要GOTO、SHIFT若干條件分支以及若干臨時的環境變數。

2. Powershell,Powered Variable

  CMD中的變數,其實都是環境變數,而且其值一定是字串。而Powershell中有真正意義的變數,並且這些變數可以是字串型別、數值型別、日期型別……甚至任意物件型別,只要是.NET庫中支援的物件。不錯,Powershell是一個物件導向的指令碼。很酷,是麼?

  CMD中如果需要列表怎麼辦——用分號分隔的字串值;那如果需要雜湊表呢——用分號分隔的帶等號的字串值……是的,CMD可以做到,只是處理起來麻煩一點而已。當然,在Powershell中不需要這麼麻煩,Powershell支援陣列型別的變數和雜湊表型別的變數,就像——嗯,像什麼呢?有點像PHP,也有點像Javascript。

  1. # 定義一個陣列 
  2. $a = 1,2,3 
  3. # 也可以這樣定義 
  4. $a = @(1,2,3
  5. # 或者定義一個空陣列 
  6. $a = @() 
  7. # 再定義兩個雜湊表 
  8. $m1 = ${ key1="value1";key2=1234 } 
  9. $emptymap = ${} 

3. 豐富的表示式

  在CMD中如果想計算四則運算,需要用到SET /A命令,可以進行常見的各種算術運算。Powershell當然不輸於CMD。Powershell中可以進行各種各樣的運算,而且完全不需要通過命令來進行。

  當然Powershell能做的不僅是這樣。比如獲取日期,CMD下需要獲取日期當然是用DATE命令,如果要乾淨一點的日期,用DATE /T,不過輸出的日期格式嘛……當然就看在Windows裡怎麼設定的啦。而在Powershell裡,日期是一個物件,格式嘛,當然可以想什麼樣就什麼樣……

  1. # 按兩種格式輸出日期 
  2. (get-date).toString("yyyy-MM-dd HH:mm:ss"
  3. # 輸出 2011-10-04 09:54:47 
  4. (get-date).toString("yyyy年M月d日"
  5. # 輸出 2011年10月4日 

  對了,還有條件表示式,Powershell是通過-eq、-ne、-lt、-gt等運算子來進行比較,還可以通過-and、-or等運算子來表達組合條件……差點忘了-not,當然它還可以簡寫成“!”。

  1. # 下面表示式返回True 
  2. (1 -lt 2) -and (3 -eq 03

4. 字串處理的天堂

  CMD中想要處理字串,那簡直就是惡夢!雖然SET和FOR命令外加GOTO或者CALL命令可以對字串進行一些簡單的處理,但是處理起來那是真的太太太複雜了。現在來到Powershell,天堂啊!字串可以非常方便地重複、拼接、拆分、各種比較,甚至匹配正規表示式,因為這些都是.NET中的String物件所具有的能力。

  1. # 輸出20個減號 
  2. "-" * 20 
  3. # 拼接為Powershell 
  4. "Powerh" + "shell" 
  5. # 匹配,以下均返回True 
  6. "powershell" -eq "POWERSHELL" 
  7. "powershell" -like "power*" 
  8. "powershell" -match "shell" 
  9. "powershell".startsWith("p"
  10. # 區分大小寫的比較和匹配,以下均返回False 
  11. "powershell" -ceq "POWERSHELL" 
  12. "powershell" -clike "P*" 
  13. "powershell".startsWith("P"

  差點忘了偉大的String.Format,格式化字串,直接看療效:

  1. # 輸出00EA 
  2. "{0:X4}" -f 234 
  3. # 上述語句等效於 
  4. [string]::format("{0:X4}"234

5. 流程控制,偉大的飛躍

  CMD當然提供了控制流程控制,因為它提供了IF命令和FOR命令。IF命令很簡單,它的方便性完全取決於條件表示式是否方便,從這一點一說,CMD的IF語句很好很強大。雖然沒有SWITCH語句是個遺憾,但至少很多個IF語句是完全撐得起場面的。但話說回來,要FOR語句撐起迴圈的一片天,還真有點吃力,所以才經常會有通過GOTO語句來模擬迴圈的情況發生。

  來到Powershell中,說起流程控制,那簡直就是一個飛躍。

  從條件分支控制來說,if和switch當然一個都不能少,而switch,更是非常的強大。switch精確匹配數值,這是常規功能;它還能匹配條件表示式,這似乎有點讓人驚喜了;它還可以按區分大小寫和不區分大小寫兩種方式匹配字串;不止這些,它還可以按萬用字元進行匹配;都到這一步了,那按正規表示式匹配也少不掉啦!

  說起Powershell的迴圈,那就更是多樣了,光Foreach都有兩種,一種是Foreach關鍵字,用於陣列地遍歷;另一種是Foreach-Object命令,用於遍歷管道輸出的多個物件。很巧……也許是Microsoft故意的,Foreach-Object有個別名,就叫Foreach。另外,For語句當然不會少,還有常見的Do…While和While {}兩種迴圈。這些都很覺,最神奇的,是Switch語句也可以用於迴圈處理陣列,並且根據陣列中各項的匹配情況來進行不同的處理——就相當於是把內嵌Switch的For/Foreach結構簡化了一樣!來個例子

  1. $array = 1..5 
  2. switch ($array) { 
  3. {$_ % 2} { "$_ is odd" } 
  4. default { "$_ is even" } 
  5. # 輸出如下 
  6. # 1 is odd 
  7. # 2 is even 
  8. # 3 is odd 
  9. # 4 is even 
  10. # 5 is odd 

6. 函式和函式庫

  CMD有函式嗎?沒有。CMD只是通過CALL模擬了函式呼叫。在指令碼內部,“CALL:標籤”和GOTO:EOF可以模擬函式呼叫及返回。而在指令碼外部,則通過“CALL 指令碼.CMD”來實現。所以CMD的函式庫,是一堆.CMD或者.BAT檔案。

  Powershell當然是支援定義函式的——通過function關鍵字。而且如果把若干函式放在一個.ps1指令碼文字中,再通過點號(.)來呼叫的話,這些函式立即對當前環境可用——也就是說,這個.ps1指令碼檔案就是函式庫。將相關的函式組織在一個指令碼檔案中當然會比組織在N個指令碼檔案中方便得多,也更利於釋出。

  當然,關鍵問題在於Powershell支援函式。Powershell的函式也可以像指令碼檔案一樣定義引數列表、命名引數和switch引數,並提供極其方便的解析功能。除此之外,Powershell的函式可以有返回值。不僅有,而且很強大,它可以以陣列的的方式返回多個值。就像這樣

  1. # 定義函式 
  2. function test([string] $a, [string] $b) { 
  3.     $a.toUpper(); 
  4.     $b.toLower(); 
  5. # 呼叫 
  6. $x, $y = test("James", "Fancy") 
  7. "`$x = $x, `$y = $y" 
  8. # 輸出 $x = JAMES, $y = fancy 

7. 其它

  Powershell的好處遠不止上面所說的那些,難怪Microsoft這麼強烈的推薦使用Powershell代替CMD。甚至有人認為Powershell將成為Windows指令碼的霸主。它的確比CMD強大了不止一點點,就是相對於WSH來說,它的便捷性和強大的.NET支援也是WSH所不能比擬的——壓根就不是一個數量級的東西。