身為 Windows 平台新一代的指令碼語言,Windows PowerShell 提供了許多用以達到模組化的功能;除了說明這些功能,本文也將討論變數、函式或篩選器的範圍。
指令碼區塊 | |
函式 | |
將引數傳入函式 | |
函式的傳回值 | |
處理管線資料的函式 | |
函式的階段處理 | |
篩選器 | |
變數範圍 | |
函式或篩選器範圍 | |
結語 |
模組化能提高程式碼重複使用的能力,藉由這種設計理念,寫過的程式碼應該可以不用重頭再寫一次,而且應該要能與其他程式碼順利接合互用。函式是一般的程式語言常見的模組化機制,Windows PowerShell 除了提供函式,還有程式碼區塊和篩選器也可用於模組化。
指令碼區塊(script block)是以大括號括住的一群 Windows PowerShell 陳述式。通常我們可以將重要、經常需要修改的程式碼放進指令碼區塊,並將指令碼區塊指定給變數,而且也將指令碼區塊放在指令碼檔案的前面。利用執行運算子&,並搭配內容為指令碼區塊的變數,我們可以執行執行指令碼區塊。例如在 Windows PowerShell 提示符號輸入以下這個簡單的例子:
PS > $a = {>> $x = 2>> $y = 3>> $x * $y>> }>>PS > &$aPS > 6
由於我們是在 Windows PowerShell 提示字元輸入指令碼區塊內容,當輸入到指令碼區塊的左大括號之後,Windows PowerShell 知道我們正在輸入指令碼區塊,因此提示字元符號會變成>>,而完成指令碼區塊的輸入除了要先輸入右大括號,還要再按一次 Enter 按鍵。
我們是將指令碼區塊指定給 $a 變數,因此若要執行這個指令碼區塊,必須利用執行運算子&:&$a,而按下 Enter 按鍵之後所顯示的 6,就是這個指令碼區塊簡例的執行結果。
如上例將指令碼區塊指定給變數之後,在關閉 Windows PowerShell 執行環境之前,我們隨時可以利用執行運算子搭配變數來執行指令碼區塊。再舉一例如下:
PS > $lsName = {>> ForEach($item in $input) {>> $item.Name }>> }>>PS > dir | &$lsName
指令碼區塊也可以配合管線運算使用,這種情況指令碼區塊會透過管線接收到名為 $input 變數,這個變數的內容是管線之前的執行結果,而上例的指令碼區塊會以 ForEach 處理藉由管線傳入的 $input 變數;這個變數內容通常是物件集合,本例的指令碼區塊會只顯示每個物件的 Name 屬性。
如果將 $lsName 搭配 Get-ChildItem(dir):dir | &$lsName,結果會顯示目前工作磁碟裡的檔案名稱;如果將 $lsName 搭配 Get-Process(ps),結果則會顯示目前執行中的行程名稱:
PS > ps | &$lsName
我們也可以將指令碼區塊用在 Windows PowerShell 指令碼檔案,例如將以下程式碼存成 ls-FileAndProcessName.ps1 之後,每次執行 ls-FileAndProcessName.ps1 的時候,就會顯示目前工作磁碟裡的檔案名稱,以及目前執行中的行程名稱。
$lsName = { ForEach($item in $input) { $item.Name }}dir | &$lsNameps | &$lsName
如上簡例,我們利用指令碼區塊重複使用顯示物件名稱的一小段程式碼,也就是將顯示物件名稱的程式碼放進指令碼區塊,並指定給變數。當我們需要這一段程式碼的時候,即可直接以執行運算子搭配變數來執行,而達到程式碼重複使用的效果。
相較於指令碼區塊,函式擁有更多的功能,用起來也更彈性(當然也比較複雜)。函式定義時必須:
• | 使用 Function 關鍵字 |
• | 必須指定函式名稱 |
• | 必須以一對大括號括住函式程式碼 |
此外,函式可以(但非必要):
• | 傳入值 |
• | 傳回值 |
我們先從簡單的例子來說明函式:
Function MySimpleFun { Write-Host "簡單函式"}
上述簡單函式執行後只是顯示「簡單函式」字串,整個函式程式碼只有一行 Write-Host "簡單函式",定義函式必須先以 Function 關鍵字啟始,空一字元後接著我們指定的函式名稱(此例為 MySimpleFun,要留意的是函式名稱不應該使用 Windows PowerShell 的關鍵字或 cmdlet 名稱、別名)。最後,函式程式碼要放在一對大括號裡。
函式可以定義在指令碼檔案,也可以在提示字元輸入。如果是後者,會像前述的指令碼區塊,提示字元符號會變成>>,輸入完函式程式碼及終止的右大括號之後,再按一次 Enter 按鍵即可恢復成正常的提示字元符號,接著直接輸入函式名稱並按 Enter 按鍵,即可執行函式;例如:
PS > Function MySimpleFun {>> Write-Host "簡單函式">> }>>PS > MySimpleFun簡單函式
如果函式是在指令碼檔案裡,也是直接以函式名稱來執行函式,但是函式定義必須在函式執行之前,例如:
# 定義 MySimpleFun函式Function MySimpleFun { Write-Host "簡單函式"}# 執行函式MySimpleFun
能傳入引數並傳回值,是函式優於指令碼區塊的兩項特點。傳入引數讓函式的功能更為彈性,例如我們可以將不同情況的參數當成引數傳入函式,讓一個函式就能處理不同情況,而不需根據不同情況編寫不同函式。
Windows PowerShell 的函式有三種接受引數傳入的方式,例如利用 Windows PowerShell 的自動變數 $Args 陣列:
PS > Function Show-Str {>> Write-Host $args[0]>> }>>PS > Show-Str HelloHello
$Args 陣列的內容會是所有傳入函式的引數,上述例子在執行 Show-Str 函式時,指定了 Hello 作為參數,因此 $args[0] 的內容會是 Hello;而我們在 Show-Str 函式裡利用 Write-Host $args[0],則可顯示傳入的第一個引數。我們其實可以如下執行 Show-Str 函式,但因為這個函式只處理第一個引數,因此不會顯示後續的引數:
Show-Str Hello Windows PowerShell
既然 $args 是陣列,我們便可利用迴圈 一一 顯示每個陣列元素的內容,例如:
PS > Function Show-Para {>> For($i = 0; $i -le $args.length-1; $i++) {>> Write-Host $args[$i]>> }>> }>>PS > Show-Para Hello Windows PowerShellHelloWindowsPowerShell
上例的 For 迴圈能處理 $args 陣列裡的每一個元素,並且以一行一個的方式顯示出來。
我們也可以指定引數的資料型別例如以下這個簡單的例子:
PS > Function AplusB {>> [int]$args[0] + [int]$args[1]>> }>>AplusB 1 99100
由於我們在上例指定了引數的資料型別為整數,因此如果執行時給予的不是整數,就會導致「無法將值轉換為 System.Int32 型別的錯誤」。
除了 $args 陣列,Windows PowerShell 的函式也能以一般常見的方式接收傳入的引數,例如:
Function AplusB([int]$x, [int]$y) { $x + $y}
這種方式最大的優點,是除了能以原本循序的方式指定參數,還能以命名參數的形式指定,例如以下前兩行都會將 x 指定為 7、y 指定為 8,如果用以下第三行的方式反而無法得到正確的答案(執行結果為 0,因為只指定 a 為 7、b 為 8,並未指定 x、y 的值):
AplusB 7 8AplusB -y 8 -x 7AplusB -a 7 -b 8
此外,這種引數傳入方式還可指定預設值,例如以下的例子指定 $x、$y 的預設值為 10、90,如果執行函式時未指定引數,計算結果會是 100:
Function AplusB([int]$x=10, [int]$y=90) { $x + $y}
最後一種,也就是第三種方式類似第二種,差別是將函式名稱後的引數列搬進大括號內的 Param,例如:
Function AplusB { Param([int]$x, [int]$y) $x + $y}
要注意的是第三種方式的 Param 必須是函式的第一行程式碼。此外,這三種引數傳入方式,如果不想指定引數資料型別,亦可刪除 [int](但就要留意型別自動轉換),或者也可以改成其他的資料型別。
Windows PowerShell 函式的傳回值與一般程式語言不太相同,例如請回想上述的 AplusB 函式,我們並未有任何傳回值的程式碼,但執行後還是會顯示相加結果:
PS > $result = AplusB 1 9
這是寫法的問題,因為函式裡直接寫著:$x + $y,對 Windows PowerShell 來說,這不僅意味著兩個變數的相加,也代表要在 console 顯示相加的結果,因此相加結果就成了傳回值。這種寫法可以讓 Windows PowerShell 函式同時有數個傳回值,例如以下這個簡單例子的3行運算結果,都是函式的傳回值(如果將以下 AandB 函式的結果指定給變數,這個變數會變成內含 3 個元素的陣列):
PS > Function AandB([int]$x=10, [int]$y=90) {>> $x + $y>> $x - $y>> $x * $y>> }>>PS > AandB 8 210616PS > $rst = AandB 8 2PS > $rst.Length3
Windows PowerShell 也提供了 Return 關鍵字,也可用來讓函式傳回值,但 Return 的重點應該是會立即結束函式、跳回呼叫函式之原處後繼續執行。例如我們將上述例子加上 Return,其結果與不加相同:
PS > Function AandB([int]$x=10, [int]$y=90) {>> $x + $y>> $x - $y>> Return $x * $y>> }>>PS > AandB 8 210616
管線是 Windows PowerShell 相當實用的功能,而只要處理 Windows PowerShell 的 $input 自動變數,我們自訂的函式也能加入管線的行列。$input 自動變數是管線之間的指令傳遞資料的橋樑,只要處理這個自動變數,我們的函式就能處理上一個指令透過管線傳來的資料。例如以下這個簡單的例子,等於只是接收上一個指令透過管線傳來的資料,但什麼也沒做就直接顯示出來:
PS > Function foo {>> $input>> }>>PS > dir | foo
$input 的內容端視管線的上一個指令而定,但根據 Windows PowerShell 的特性,$input 的內容通常會是集合物件,因此我們可以利用 ForEach 之類的迴圈來處理 $input 的內容,例如:
…ForEach($item in $input) { $item.Name …}…
上述簡例可處理管線上一指令的結果,如果要讓函式執行結果透過管線傳入下一個指令,其實並不需要自己處理 $input 變數,因為 Windows PowerShell 的管線功能會自行將管線上一個指令的傳回值置入 $input 變數,例如以下這個簡單的例子,最後的執行結果是顯示 101 到 1001 等 10 個整數值:
Function foo1 { # 以 For 迴圈產生 1~9 等整數,並直接傳回 For($i=1; $i -le 10; $i++) { # 直接傳回所產生的整數值 $i }}Function foo2 { # 將$input的內容都 * 100 + 1 foreach($item in $input) { $item * 100 + 1 }}foo1 | foo2
Windows PowerShell 的函式還可分成以下 3 個處理階段的程式碼區塊:
• | Begin:只會在函式一開始時執行一次,適合放置初始動作的程式碼,例如初始變數。 |
• | Process:至少執行一次,但有可能重複執行數次。 |
• | End:只會在函式結束前執行一次。 |
這 3 階段並非必要,而且也不是全部都要,例如可以只取 Begin 和 Process 兩階段使用。再者,若使用函式階段處理功能,在階段區塊以外的地方,不能有任何程式碼,而且每個階段只能出現一次,否則都會造成執行錯誤。
以下這個例子說明了含階段處理的用法,例中 foo 函式有 3 階段,Begin 和 End 只會在函式執行之初及結束前執行一次,但 Process 可能會重複執行數次;此例因以管線將 dir 結果傳入 foo 函式,每傳入一次,就會讓 Process 區塊執行一次:
Function foo { Begin { "Begin…" # 造成空一行的效果 " " $i = 1 } Process { "Process " + $i $_.Name $i ++ # 造成空一行的效果 " " } End { "The End." }}dir | foo
Windows PowerShell 的篩選器與函式類似,差別在於:
• | 篩選器的關鍵字為 Filter(函式為 Function)。 |
• | 若管線傳入資料,篩選器是一次收到傳入資料的一個物件,傳入篩選器的資料(物件)是放在 $_ 變數,而且每傳入一個物件,篩選器就會執行一次(相對的,函式是一次收到傳入資料的一整個集合物件,這個集合物件是放在 $input 變數)。 |
也就是說:管線資料是一次、而且是以集合物件($input)傳入函式,因此函式要以迴圈來處理集合物件裡的每個物件;而管線資料每次傳入一個物件($_)到篩選器,每傳入物件一次,篩選器就執行一次,直到所有的資料傳完。例如以下兩個簡單的例子:
PS > Function foo-fun {>> $_.Name>> }>>PS > Filter foo-fil {>> $_.Name>> }>>
兩個例子都以 $_ 變數處理管線傳入的物件(顯示物件的 Name 屬性),第一個例子是函式,第二個例子是篩選器。若分別執行 dir | foo-fun和dir | fii-fil,會發現前者未能顯示任何東西,而後者顯示目前工作磁碟裡的檔案名稱。如果要 foo-fun 得到相同於 foo-fil 的結果,要改寫成以 ForEach 處理 $input。
但函式還是可以使用 $_,並且達到與篩選器相同的效果;前述函式階段處理的例子就是:管線一次將資料放在 $input,並且執行該例函式一次,但因為函式裡有 Process 程式碼區塊,因此會自動一次次的從 $input 取出 $_,並且重複執行 Process 程式碼區塊,直到 $input 裡的所有物件都處理過,而達到如下的效果:
ForEach($_ in $input)
也就是說,內含 Process 程式碼區塊的函式,其實就有等同於篩選器的效果。
本文的最後一個主題是「範圍」。與範圍關係密切的是變數、函式和篩選器,討論範圍的目的,是為了瞭解變數、函式和篩選器的「可及領域」。Windows PowerShell 變數、函式或篩選器的範圍有三種層級,如下圖:
全域指的是 Windows PowerShell 的執行環境,也就是有提示字元、可以輸入指令或指令碼檔案的視窗。範圍的基本規則是:除非明確指定,否則只能在建立變數的範圍內讀取、修改變數,而且建立變數所在範圍的的下一層範圍,只能夠讀取上層範圍所建立變數;在上層範圍不能讀也不能改下層範圍所建立的變數。
開啟 Windows PowerShell 執行環境之後,隨即進入全域範圍。此時若執行指令碼檔案、呼叫函式、甚至再從中開啟另一個 Windows PowerShell 執行環境,都會建立新的下層範圍(或稱子系範圍)。在下層範圍可以讀取上層範圍(或稱父系範圍)建立的變數,但除非明確的指出範圍,否則不能更改上層範圍的變數。
我們以下面這個簡單的指令碼例子來說明:
# 指令碼層級$var = "A"Function foo { # 函式層級 "2- " + $var $var = "B" "3- " + $var}"1- " + $varfoo"4- " + $var
請務必將以上範例儲存在指令碼檔案,再於 Windows PowerShell 環境執行。執行的結果是:
1- A2- A3- B4- A
這個指令碼檔案一開始就將 $var 變數的值指定成 A(因此 $var 是指令碼層級的變數),接著是函式 foo,然後:
1. | 執行指令碼範圍的 "1- " + $var:由於一開始就將 $var 變數的值指定成 A,所以顯示 1- A。 |
2. | 呼叫 foo 函式,執行 foo 函式裡的 "2- " + $var:函式或篩選器有自己的範圍,但因為指令碼變數的範圍可及函式,Windows PowerShell 的下層能讀取上層的變數,而且 $var 變數的值依然是 A,所以顯示 2- A。 |
3. | 執行 foo 函式裡的 $var = "B":這裡請注意,Windows PowerShell 的下層只能讀取上層的變數,但不能修改,因此如果要將 $var 變數的值改成 B,結果是建立屬於 foo 函式範圍的 $var 變數(雖然同名,但這是獨立於上層 $var 的區域變數),並將這個 $var 的值指定成 B。所以並非上層的 $var 變數被改成 B,而是另建了屬於 foo 函式層級的同名變數,而且其值為 B。 |
4. | 執行 foo 函式裡的 "3- " + $var:此時在foo函式已經有自己的 $var 變數,所以顯示 3- B。 |
5. | 離開 foo 函式、回到函式呼叫原處繼續執行 "4- " + $var:此時已跳出函式,回到上層的指令碼層級,這個範圍的 $var 變數值為 A,因此顯示 4- A。 |
剛才曾提及一項變數範圍的規則:除非明確指定,否則只能在建立變數的範圍內讀取或修改變數;Windows PowerShell 提供了 3 種識別變數範圍的標籤:local、global、script,利用這些標籤,就能指定要讀取或修改變數的範圍。
local 範圍就是目前的範圍,只要執行函式或指令碼,或者開啟新的 Windows PowerShell 執行環境,就會建立新的 local 範圍。global 範圍是 Windows PowerShell 開啟所建立的範圍,在下層範圍(包括指令碼或函式、篩選器)只要指明 global 標籤,就能更改 global 範圍所建立的變數(但在下層範圍不需指明 global 標籤,就可以讀取 global 範圍所建立的變數)。
script 範圍是執行指令碼所建立的範圍,當指令碼結束,該範圍也就終止。指明變數的範圍標籤,是因為在下層範圍想要更改上層範圍的變數值,因此 script 標籤是用在函式或篩選器裡。要指明變數的範圍標籤,請在變數名稱之前、$符號之後,加入適當的標籤名稱及冒號,例如指明 script 範圍的 $var 變數是為:
$script:var
現在我們略微調整上一個範例,將 foo 函式裡的更改 $var 變數值的陳述式指明 script 範圍,如下:
# 指令碼層級$var = "A"Function foo { # 函式層級 "2- " + $var $script:var = "B" "3- " + $var}"1- " + $varfoo"4- " + $var
同樣的將以上範例儲存在指令碼檔案再執行,你認為結果會有怎麼樣的變化?以下是執行結果:
1- A2- A3- B4- B
為什麼最後一項從之前的 A 變成現在的 B?因為指明了 script 範圍,因此 $script:var = "B" 所更改的變數,是上一層指令碼範圍裡的 $var。
避免範圍困擾的方法之一,是指明變數的範圍標籤,例如:
$script:var = "init"Function chgvar { $script:var $local:var = "function" $local:var $script:var = "script" $script:var}chgvar$local:var
此外,Windows PowerShell 也提供了僅在建立範圍有效的私有變數,例如:
$var = "a"Function foo1 { $var $private:var = "b" $var foo2}Function foo2 { $var}$varfoo1$var
執行結果如下:
aabaa
同樣的範圍概念也適用於函式或篩選器。函式或篩選器的範圍僅及建立之處及下層,因此定義在指令碼檔案裡的函式或篩選器,不能用在上層提示環境。而如果上、下層範圍皆定義了同名的函式或篩選器,下層範圍會優先使用該區域所定義的函式或篩選器,除非以範圍標籤指明函式或篩選器。前述的 local、script、global、private 關鍵字不只能用在變數,也能用在函式或篩選器。
巢狀的函式
Windows PowerShell 允許函式以巢狀的形式出現,例如:
# 外部函式 AFunction A { Function A1 { # 內部函式 A1 的程式碼 } Function A2 { # 內部函式 A2 的程式碼 } 函式 A 的程式碼}
根據上述規則,只有在函式 A 之內才能呼叫函式 A1、A2(函式 A1、A2 彼此也能呼叫)。
Windows PowerShell 提供了許多模組化的機制,目的是要便於程式碼的重複使用。本文提及了使用 Windows PowerShell 所能應用的模組化功能,包括程式碼區塊、函式或篩選器。變數、函式或篩選器的範圍則是本文另一項重點,瞭解這些規則才能完全掌握變數、函式或篩選器的使用。
自由广告区 |
分类导航 |
邮件新闻资讯: IT业界 | 邮件服务器 | 邮件趣闻 | 移动电邮 电子邮箱 | 反垃圾邮件|邮件客户端|网络安全 行业数据 | 邮件人物 | 网站公告 | 行业法规 网络技术: 邮件原理 | 网络协议 | 网络管理 | 传输介质 线路接入 | 路由接口 | 邮件存储 | 华为3Com CISCO技术 | 网络与服务器硬件 操作系统: Windows 9X | Linux&Uinx | Windows NT Windows Vista | FreeBSD | 其它操作系统 邮件服务器: 程序与开发 | Exchange | Qmail | Postfix Sendmail | MDaemon | Domino | Foxmail KerioMail | JavaMail | Winwebmail |James Merak&VisNetic | CMailServer | WinMail 金笛邮件系统 | 其它 | 反垃圾邮件: 综述| 客户端反垃圾邮件|服务器端反垃圾邮件 邮件客户端软件: Outlook | Foxmail | DreamMail| KooMail The bat | 雷鸟 | Eudora |Becky! |Pegasus IncrediMail |其它 电子邮箱: 个人邮箱 | 企业邮箱 |Gmail 移动电子邮件:服务器 | 客户端 | 技术前沿 邮件网络安全: 软件漏洞 | 安全知识 | 病毒公告 |防火墙 攻防技术 | 病毒查杀| ISA | 数字签名 邮件营销: Email营销 | 网络营销 | 营销技巧 |营销案例 邮件人才:招聘 | 职场 | 培训 | 指南 | 职场 解决方案: 邮件系统|反垃圾邮件 |安全 |移动电邮 |招标 产品评测: 邮件系统 |反垃圾邮件 |邮箱 |安全 |客户端 |