前段時(shí)間公司封閉開發(fā),就在封閉的前一天感冒發(fā)燒,為了封閉,一頓猛藥下去,燒是退了,卻在扁桃附近爆發(fā)出來——扁桃發(fā)炎加潰瘍,搞了十多天才好啊,天天喝稀飯啊……所以請大家原諒這么久沒有續(xù)上學(xué)習(xí)筆記。順便:過兩天繼續(xù)封閉,所以這個(gè)筆記更新速度可能不會很快了,我盡力。
函數(shù)是Powershell里一個(gè)非常重要的東西,與CMD比較起來,這絕對是一個(gè)亮點(diǎn)。CMD中只能用“標(biāo)簽”+CALL來模擬函數(shù),而Powershell不僅支持函數(shù),還支持3種類型的函數(shù):普通函數(shù)(Function)、過濾器(Filter)和管道函數(shù)(Pipeline Function)。除此之外,Powershell的參數(shù)解析也是非常智能和強(qiáng)大——當(dāng)然,參數(shù)形式的約定是必不可少的部分。
函數(shù)可以為一系列的操作提供一個(gè)快捷命令,而且還可以通過參數(shù)來改變函數(shù)內(nèi)的流程或運(yùn)算結(jié)果。還是先來一個(gè)示例:
- PS C:\Users\james> f:
- PS F:\> function cd~ { cd c:\users\james }
- PS F:\> cd~
- PS C:\users\james>
這里定義了一個(gè)名為“cd~”的函數(shù),目的是直接回到用戶目錄,就像Linux的“cd ~”一樣。這里function是定義函數(shù)的關(guān)鍵字,cd~是函數(shù)名,{}中的部分則是函數(shù)體?!@里還沒用到參數(shù),這個(gè),后面再說。執(zhí)行“cd~”,實(shí)際是執(zhí)行了函數(shù)體里面的內(nèi)容。
當(dāng)然,如果一個(gè)函數(shù)比較復(fù)雜,要一行寫完是比較痛苦的。那么也可以分多行來寫,比如
- PS C:\users\james> function cd~ {
- >> cd c:\users\james
- >> }
- >>
- PS C:\users\james>
當(dāng)然,這樣寫不太方便,因?yàn)槟阍趯懙阶詈笠恍袝r(shí)如果發(fā)現(xiàn)前面有錯(cuò),連修改的機(jī)會都沒有。不過,如果把一段程序或者函數(shù)定義寫在腳本文件中,再來執(zhí)行腳本就會方便得多了——我想,關(guān)于腳本文件,在前面已經(jīng)說過,這里就不用多說了。
為了演示參數(shù),我們用另一個(gè)例子。這個(gè)例子會將參數(shù)轉(zhuǎn)為大寫輸出:
- PS F:\> function toUpper { $args[0].toUpper() }
- PS F:\> toUpper "james"
- JAMES
- PS F:\>
這個(gè)示例中,用到了未命名參數(shù)數(shù)組$args。只要是沒有命名的參數(shù)都會按順便存在在這個(gè)數(shù)組中。如果是多個(gè)未命名參數(shù),我們可以用用一個(gè)循環(huán)來依次處理。比如:
- PS F:\> function toUpper {
- >> foreach ($a in $args) { $a.toUpper() }
- >> }
- >>
- PS F:\> toupper hello james fancy
- HELLO
- JAMES
- FANCY
- PS F:\>
循環(huán)是控制流程的內(nèi)容,之前的筆記還沒提到。這篇筆記主要是記函數(shù),所以也暫時(shí)略過不說。
不過這里有一個(gè)問題卻不得不說——未命名參數(shù)。顧名思義,未命名參數(shù)就是沒有名字的參數(shù);而且,既然有未命名參數(shù),就一定有命名參數(shù)。那么命名參數(shù)又是什么呢?還是看例子(這次的例子是寫的腳本文件):
- # sample.ps1
- # 命名參數(shù)示例
- function hello($name, $isMale) {
- if ([bool]::parse($isMale)) {
- "Hello Mr. $name"
- } else {
- "Hello Ms. $name"
- }
- }
- hello James true # 參數(shù)值為按順便賦予命名參數(shù)
- hello -ismale false Jenny # 指定了名稱的參數(shù)值會賦給對應(yīng)的命名參數(shù),其它的按順序賦給其它命名參數(shù)
運(yùn)行結(jié)果如下:
- PS F:\james\Desktop> .\sample.ps1
- Hello Mr. James
- Hello Ms. Jenny
上面的示例中有兩個(gè)調(diào)用函數(shù)的示例。第一個(gè)沒有為參數(shù)值指定名稱,那么函數(shù)調(diào)用時(shí)會按順序把參數(shù)值賦給參數(shù)列表中的命名參數(shù);而第二種調(diào)用,為ismale參數(shù)指定了值,那么會先將指定了名稱的參數(shù)值賦給相應(yīng)的命名參數(shù),其它的參數(shù)值再按順序賦予其它命名參數(shù)?,F(xiàn)在有一個(gè)問題:如果賦予所有命名參數(shù)之后還有參數(shù)傳入,這些參數(shù)是否可以通過$args來訪問呢?繼續(xù)做實(shí)驗(yàn),把上面的示例稍做改動(dòng):
- # sample.ps1
- # 多余的參數(shù)示例
- function hello($name, $isMale) {
- if ([bool]::parse($isMale)) {
- "Hello Mr. $name"
- } else {
- "Hello Ms. $name"
- }
- foreach ($a in $args) {
- "MORE ARG: $a"
- }
- }
- hello James true a b c d e f g
運(yùn)行結(jié)果
- PS F:\james\Desktop> .\sample.ps1
- Hello Mr. James
- MORE ARG: a
- MORE ARG: b
- MORE ARG: c
- MORE ARG: d
- MORE ARG: e
- MORE ARG: f
- MORE ARG: g
其實(shí)多做做實(shí)驗(yàn),答案都是顯而易見的。繼續(xù)下一個(gè)問題:“[bool]::parse($isMale)”能不能簡化?[bool]::parse其實(shí)是調(diào)用了System.Boolean的靜態(tài)方法Parse來將字符串解析為布爾類型的值。如果我們直接傳入布爾類型的參數(shù)不就可以簡化了么?就像這樣
- # sample.ps1
- function hello($name, $isMale) {
- if ($isMale) {
- "Hello Mr. $name"
- } else {
- "Hello Ms. $name"
- }
- }
- hello James $true
- hello -ismale $false Jenny
其實(shí),還可以更簡單,比如對于男士,省略第2個(gè)參數(shù)……當(dāng)然,現(xiàn)在這個(gè)腳本不行,試試就知道了,可以省略$isMale參數(shù)的是女士。再做點(diǎn)改動(dòng):
- # sample.ps1
- # 默認(rèn)參數(shù)值示例
- function hello($name, $isMale=$true) {
- if ($isMale) {
- "Hello Mr. $name"
- } else {
- "Hello Ms. $name"
- }
- }
- hello James
- # Hello Mr. James
既然isMale是一個(gè)布爾,在控制臺腳本里,會很容易讓人想起“開關(guān)”,即通過一個(gè)參數(shù)是否存在來表示兩種不同的參數(shù)值。比如,如果加了“-ismale”參數(shù),則表示男士,否則表示女士——哦,應(yīng)該換一下,因?yàn)椴患訁?shù)為默認(rèn)形式,而我們前面約定的默認(rèn)情況是男士,所以開關(guān)參數(shù)應(yīng)該改為“-isFemale”,不過既然是開關(guān),那“is”也可以省了,就是“-female”。示例:
- # sample.ps1
- # [switch]參數(shù)值示例
- function hello($name, [switch] $female) {
- if (!$female) {
- "Hello Mr. $name"
- } else {
- "Hello Ms. $name"
- }
- }
- hello James
- hello Jessy -female
- # Hello Mr. James
有注意到定義參數(shù)時(shí)指定的[switch]標(biāo)記么?這叫參數(shù)類型。當(dāng)然[switch]只是參數(shù)類型中的一種……
除此之外,還可以為參數(shù)指定類型,這樣的話,只要給予的參數(shù)值不是指定的類型,或者不能轉(zhuǎn)換為指定的類型,就會拋出錯(cuò)誤。當(dāng)然,指定了類型的參數(shù),在函數(shù)內(nèi)進(jìn)行處理時(shí),往往可以活力掉類型轉(zhuǎn)換的步驟,比如我們想把年、月、日3個(gè)參數(shù)拼成一個(gè)8位長度的日期字符串,下面哪個(gè)函數(shù)是可以完成呢?
- # sample.ps1
- # 指定類型的參數(shù)示例
- function add1($a, $b, $c) {
- $a + $b + $c
- }
- function add2([string]$a, $b, $c) {
- $a + $b + $c
- }
- add1 2010 10 21 # 輸出:2042
- add2 2010 10 21 # 輸出:20111021
由于沒有指定類型,add1的三個(gè)參數(shù)都被當(dāng)作int型進(jìn)行處理,相加的結(jié)果是2042。而add2中,將$a申明為string類型,雖然$b和$c仍然是被當(dāng)作int型進(jìn)行處理,但是“+”遇到不同類型的運(yùn)算時(shí)是自動(dòng)轉(zhuǎn)為其左邊的類型進(jìn)行運(yùn)算,所以是字符串相連,結(jié)果20111021。
Powershell關(guān)于函數(shù)返回值這個(gè)問題,比較復(fù)雜。在其它腳本或者語言中,通常來說,通過return之類的關(guān)鍵返回的才是返回值,而Powershell不同,只有是輸出到Output的內(nèi)容,都是返回值,比如
- # sample.ps1
- function test() {
- write-output "Hello"
- "James Fancy"
- return "OK"
- }
- $a = test
- $a.GetType().FullName
- $a
它的輸出:
- PS F:\james\Desktop> .\sample.ps1
- System.Object[]
- Hello
- James Fancy
- OK
可以看出來,test函數(shù)返回了包含3個(gè)值的一個(gè)數(shù)組,除了最后的return外,前面兩個(gè)都是寫入管道的。哦,管道……這又是一個(gè)復(fù)雜的東西,后面再來復(fù)習(xí)。現(xiàn)在只需要記得write-output輸出,直接字面值或者變量值輸出以及return都會產(chǎn)生返回值就對了。
很明顯,Powershell的函數(shù)允許一次返回多個(gè)值,這些值都保存在一個(gè)數(shù)組中。當(dāng)然,如果函數(shù)只返回一個(gè)值,那就不需要數(shù)組了,比如把上面的示例精簡一下:
- # sample.ps1
- function test() {
- "James Fancy"
- }
- $a = test
- $a.GetType().FullName # 輸出:System.String
- $a # 輸出:James Fancy
除了定義函數(shù)之外,也可以定義過濾器。過濾器可以對通過管道進(jìn)來的內(nèi)容進(jìn)行過濾,比如下面這個(gè)列子就是為了只列出.exe文件:
- # sample.ps1
- filter test() {
- # 只列出.exe擴(kuò)展名的文件
- if ($_.extension -eq ".exe") { $_ }
- }
- dir | test
運(yùn)行結(jié)果:
- PS E:\james\Desktop> cd c:/windows
- PS C:\windows> E:\james\Desktop\sample.ps1
- 目錄: C:\windows
- Mode LastWriteTime Length Name
- ---- ------------- ------ ----
- -a--- 2009-7-14 9:14 65024 bfsvc.exe
- -a--- 2011-6-9 11:10 642240 bjzq.exe
- -a--- 2010-1-31 15:23 2614272 explorer.exe
- -a--- 2009-7-14 9:14 13824 fveupdate.exe
- -a--- 2009-7-14 9:14 497152 HelpPane.exe
- -a--- 2009-7-14 9:14 15360 hh.exe
- -a--- 2011-3-21 23:21 78848 KMSEmulator.exe
- -a--- 2011-3-21 23:26 151552 KMService.exe
- -a--- 2009-11-28 7:52 179712 notepad.exe
- -a--- 2009-7-14 9:14 398336 regedit.exe
- -a--- 2009-6-11 5:41 49680 twunk_16.exe
- -a--- 2009-7-14 9:14 31232 twunk_32.exe
- -a--- 2009-6-11 5:42 256192 winhelp.exe
- -a--- 2009-7-14 9:14 9728 winhlp32.exe
- -a--- 2009-7-14 9:14 9216 write.exe
- -a--- 2011-8-10 12:27 34512 xinstaller.exe
- PS C:\windows>
注意腳本中最后一句“dir | test”,意思就是把dir的輸出通過管道傳遞給test進(jìn)行處理。再看test函數(shù)的內(nèi)容,管道中傳入的每一項(xiàng)都由特殊變量$_引用。test對傳入的每一項(xiàng)都進(jìn)行判斷,將擴(kuò)展名為.exe的文件對象輸出,其余的丟棄。
其實(shí),過濾器是一種特殊的函數(shù),管道函數(shù)的簡化版。管道函數(shù)也是一種特殊的函數(shù),它包含3個(gè)部分,begin、process和end。管道輸出在進(jìn)入管道函數(shù)的時(shí)候,會首先運(yùn)行begin區(qū)域的腳本,僅運(yùn)行一次;之后從管道進(jìn)來的每個(gè)對象都會經(jīng)歷process過程;所有項(xiàng)結(jié)束之后,會觸發(fā)end區(qū)域的腳本。而過濾器就是只定義了process區(qū)的管理函數(shù)。還是來看例子:
- # sample.ps1
- function test() {
- begin {
- "處理開始了"
- }
- process {
- if ($_ -like "a*") { $_ }
- }
- end {
- "處理完成了"
- }
- }
- # 這次示例用一個(gè)數(shù)組來演示
- $("a", "ab", "bac", "b", "bc", "ac") | test
輸出:
- PS E:\james\desktop> E:\james\Desktop\sample.ps1
- 處理開始了
- a
- ab
- ac
- 處理完成了
管道函數(shù)的3個(gè)塊都可以省略,包括process塊,只不過如果省略了process塊之后,這個(gè)函數(shù)就沒啥意義了。不過根據(jù)實(shí)際情況,begin和end塊倒是經(jīng)常被省略的。
前面關(guān)于運(yùn)算符的筆記中提到了點(diǎn)號(.)運(yùn)算符可以用于引入一個(gè)腳本,而這個(gè)腳本就類似于C/C++中#include的方式被引入到當(dāng)前位置并執(zhí)行。那么,如果這個(gè)腳本里面只包含函數(shù)定義,而不包含其它內(nèi)容,那么這個(gè)腳本就是一個(gè)函數(shù)庫。每次使用該函數(shù)庫的時(shí)候,只需要使用點(diǎn)號運(yùn)算符引入即可。比如上面的例改稍作改動(dòng):
- # sample.ps1
- function test() {
- process {
- if ($_ -like "a*") { $_ }
- }
- }
然后在Powershell控制臺運(yùn)行:
- PS E:\james\desktop> . .\sample.ps1
- PS E:\james\desktop> $("a", "ab", "bac", "b", "bc", "ac") | test
- a
- ab
- ac
- PS E:\james\desktop>
如果有興趣試試不使用點(diǎn)號,而是直接運(yùn)行腳本,那么第二條命令就會出錯(cuò)是,因?yàn)閠est未定義。
更多建議: