天天看點

Windows PowerShell 2.0語言之函數和過濾器

本文讨論了PowerShell提供的業務控制機制函數和過濾器,函數是用來擴充内置shell功能最常見的方法,盡快掌握函數非常重要,會便于對業務進行封裝。“分而治之”的政策在PowerShell中重要而有效,使用者能夠将劃分後的子問題使用函數來解決,最後可以通過将多個函數聯合起來的方法解決高難度的問題。使用者需要逐漸熟悉和習慣将複雜的問題劃分成為多個較為簡單的小問題,單個小問題劃分到相對獨立而易于處理的程度後單獨處理,這樣解決問題的成本會降低。劃分問題的模式不是固定的,劃分的标準一般是盡量降低子產品間的耦合度,不可避免的耦合關系可以通過函數間變量的傳遞實作資料的交換。這樣單個函數的功能較為獨立,便于代碼的重複使用,也便于出現錯誤時的調試和排錯。

1 定義函數

函數的可執行代碼塊類似腳本塊,不同之處在于腳本塊是匿名的,在執行之前會被指定給一個變量;函數在建立時擷取名稱,函數名會立即生效并可以執行。定義函數使用function關鍵字,其格式如下:

function <name>(<parameter list>)

{

  <function body>

}

函數名應該以字母開頭,包含字母和數字的組合,以及連字元(-)和下畫線(_)。下例使用函數輸出字元:

PS C:\>function Say-Hello()

>>  {

>>    Write-Host “Hello World from a function in PowerShell.”

>>   }

>>

PS C:\> Say-Hello

PS C:\> Hello World from a function in PowerShell.

參數清單中的圓括号為可選,不接收參數時可以省略,如:

PS C:\>function Say-Hello

定義函數的過程中會在目前作用域中自動建立同名對象,是以在調用函數時使用函數名即可,如:

1.1 函數體

在上一節的執行個體中使用了Verb-Noun的形式命名函數,這種動詞-名詞的形式是PowerShell中為了清楚和易讀而約定俗成的命名規則。函數的執行動作類似于内建的cmdlet,是以命名形式也是類似的。

函數注冊在全局指令命名空間中,它的調用和其他cmdlet中的調用相同。可以使用Get-Command擷取之前定義函數的引用。下例擷取之前定義的函數:

PS C:\> $helloFunction = Get-Command Say-Hello -type Function

PS C:\> $helloFunction.GetType().FullName

System.Management.Automation.FunctionInfo

這裡擷取的是這個函數是個System.Management.Automation.FunctionInfo對象,其中包含的有用屬性如下;

PS C:\> $helloFunction | gm -type Property

   TypeName: System.Management.Automation.FunctionInfo

Name                MemberType Definition

----                ---------- ----------

CmdletBinding       Property   System.Boolean CmdletBinding {get;}

CommandType         Property   System.Management.Automation.

CommandTypes CommandType {get;}

DefaultParameterSet Property   System.String DefaultParameterSet {get;}

Definition          Property   System.String Definition {get;}

Description         Property   System.String Description {get;set;}

Module              Property   System.Management.Automation.

PSModuleInfo Module {get;}

ModuleName          Property   System.String ModuleName {get;}

Name                Property   System.String Name {get;}

Options             Property   System.Management.Automation.

ScopedItemOptions Options {get;set;}

Parameters          Property   System.Collections.Generic.

Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Cu...

ParameterSets       Property   System.Collections.

ObjectModel.ReadOnlyCollection`1[[System.Management.

Automation.Com...

ScriptBlock         Property   System.Management.

Automation.ScriptBlock ScriptBlock {get;}

Visibility          Property   System.Management.

Automation.SessionStateEntryVisibility Visibility

{get;set;}

CommandType屬性已經是Function,Name屬性是Say-Hello。ScriptBlock屬性可以在不檢視文檔的情況下顯示在目前函數中包含的功能。下例擷取目前函數的資訊:

PS C:\> $helloFunction.ScriptBlock

   Write-Host " Hello World from a function in PowerShell."

PS C:\> &$helloFunction.ScriptBlock

Hello World from a function in PowerShell.

上例中使用了調用操作符(&),可以在執行函數之前檢查函數腳本塊的内容。函數對象的Definition屬性以字元串的形式包含函數代碼。下例示範如何使用這個屬性:

PS C:\> $helloFunction.Definition.GetType().FullName

System.String

PS C:\> $helloFunction.Definition

   Write-Host "Hello World from a function in PowerShell."

Definition屬性包含所有有效的可操作代碼,甚至可以通過Invoke-Expression執行函數,如:

PS C:\> Invoke-Expression $helloFunction.Definition

看起來上例的執行方式對于函數來說并沒有優勢,但在某些場合中确實很友善。

1.2 函數參數

為了讓函數接收參數,可以在函數定義中指定參數清單。下例接收兩個參數并将其和輸出到控制台:

PS C:\> function Write-Sum($first,$second)

>> {

>>   $sum = $first + $second

>>   Write-Host "Sum:$sum"

>> }

PS C:\> Write-Sum 6 9

Sum:15

【提示】

大多數程式設計語言需要使用者在括号中提供參數,并用逗号作為分隔符,如Write-Sum(6,9)。在PowerShell中調用函數的文法與調用外部程式和cmdlet相同,并以空格分隔參數清單,或者對命名參數使用基于開關的文法,如Write-Sum –first 6 –second 9。

在PowerShell中用圓括号和逗号的形式提供參數對象方法,如$myObject.DoSometing(4,5)。盡管二者相似,但是卻有很大差别。

在函數中也可以使用前面章節中腳本塊參數使用的特性,所有在腳本塊中對參數執行的操作在函數參數操作中均可使用。可以指定參數的類型,程式解釋器會自動轉換傳遞給函數的參數類型。下例在函數中強制類型轉換為×××參數:

PS C:\> function Write-Sum([int]$first, [int]$second)

>>  $sum = $first + $second

>>  Write-Host "Sum: $sum"

PS C:\> Write-Sum 5 "9"

Sum: 14

如果沒有傳遞某個參數,可以使用程式提供的預設值。下例格式化日期并輸出到控制台:

PS C:\> function Format-Date($date,  $format = "{0:M/d/yyyy}")

>> Write-Host ($format -f $date)

PS C:\> Format-Date [datetime]::Today

[datetime]::Today

PS C:\> Format-Date ([datetime]::Today)

2/4/2009

預設日期格式是“月/日/年”。如果要使用完整格式的日期輸出,可以覆寫上例中的格式參數:

PS C:\> Format-Date ([datetime]::Today) "{0:f}"

2009年2月4日 0:00

可以強制通過抛出異常的形式使用預設值,由函數調用者傳遞一個參數。下例的輸入日期強制要求參數:

PS C:\> function Format-Date($date = $(throw "Date required"),`

>>                       $format = "{0:M/d/yyyy}")

>>         Write-Host ($format -f $date)

PS C:\> Format-Date

Date required

At line:1 char:37

+ function Format-Date($date = $(throw <<<<  "Date required"),`

    + CategoryInfo          : OperationStopped: (Date required:String) [], RuntimeException

    + FullyQualifiedErrorId : Date required

可以在定義函數時跳過參數聲明,而在函數體中聲明。函數體本身以腳本塊的形式存在,可以包含param語句。下例中的Format-Date函數在腳本塊中聲明變量:

PS C:\PowerShell> function Format-Date

>> param ($date = $(throw "Date required"), $format = "{0:M/d/yyyy}")

PS C:\PowerShell> Format-Date (Get-Item C:\autoexec.bat).LastAccessTime

3-26-2009

PS C:\PowerShell>

1.3 通過引用傳遞參數

用參數傳遞資料給函數隻是單方面的,不能通過為參數指派而改變參數的原有值。為參數賦新值時,隻是簡單地在本地建立了同名變量,如:

PS C:\> $name = "LiMing"

PS C:\> function AssignValueToParam($name)

>> $name = "WangLei"

>> Write-Host "inside function: $name"

PS C:\> AssignValueToParam $name

inside function: WangLei

PS C:\> Write-Host "outside function: $name"

outside function: LiMing

新建立的變量會在目前作用域中覆寫之前傳遞的參數,原參數值不變,為改變傳遞到函數中的參數值,可以使用Get-Variable和Set-Variable在複雜的作用域間更改變量值。下例建立的函數用來交換兩個變量值:

PS C:\> function SwapValue($first, $second)

>>     $firstValue = Get-Variable $first -scope 1 -valueOnly

>>     $secondValue = Get-Variable $second -scope 1 -valueOnly

>>     Set-Variable $first $secondValue -scope 1

>>     Set-Variable $second $firstValue -scope 1

PS C:\> $a = 5

PS C:\> $b = 8

PS C:\> SwapValue "a" "b"

PS C:\> $a

8

PS C:\> $b

5

上例中雖然達到了預定的功能,但是代碼當中是存在局限性的。交換值的變量定義在父作用域中,如果要交換父作用域中的兩個變量,則無效。

在PowerShell中可以通過引用PSReference對象的變量,這樣可以使用[ref]的類型标記來将任何變量轉換為PSReference對象,進而修改Value屬性的對象,設定引用對象的value屬性将修改原始的變量。下例使用引用方式重寫上面的SwapValue函數:

PS C:\> function SwapValue([ref] $first,[ref] $second)

>> $tmp = $first.Value

>> $first.Value = $second.Value

>> $second.Value = $tmp

PS C:\> $a = 6

PS C:\> $b = 3

PS C:\> SwapValue ([ref] $a) ([ref]$b)

3

6

在引用變量的過程中并沒有限定搜尋的作用域,這樣代碼的使用範圍更廣。代碼中調用SwapValue時使用圓括号使得變量的[ref]強制類型轉換在将對象傳遞給函數之前完成,進而保證了對象類型的有效。

1.4 傳回值

可以通過從函數中輸出未被任何操作銷毀的對象來傳回值,輸出多個對象将會傳回包含多個對象的集合。下面的函數中在循環中輸出多個對象:

PS C:\> function Generate-NumberTo($max)

>>     for($i=0; $i -lt $max; $i++)

>>     {

>>         $i

>>     }

PS C:\> Generate-NumberTo 4

1

2

可以使用return語句在退出函數的同時傳回值,下例中的函數在集合中搜尋對象:

PS C:\> function Find-Object($target, $haystack)

>>     foreach ($item in $haystack)

>>         if($item -eq $target)

>>         {

>>             return $item

>>         }

PS C:\> Find-Object 5 (2..19)

PS C:\> Find-Object 5 (7..19)

PS C:\>

需要強調的是輸出對象與将對象寫到控制台之間的差別,前者是将對象傳遞到輸出流中的動作。可以用變量儲存cmdlet傳回的對象,或調用指令将對象寫到控制台。下例把對象寫到控制台:

PS C:\> function Get-TextFiles()

>>     dir *.txt

PS C:\> Get-TextFiles

    Directory: C:\

Mode                LastWriteTime     Length Name

----                -------------     ------ ----

-a---          2009/1/3     14:31         12 digit.txt

-a---          2009/1/3      6:06      12100 largetext.txt

-a---          2009/1/3      6:07       6949 smalltext.txt

-ar--          2009/1/3      6:27         13 test.txt

-a---          2009/1/3      6:28          9 test2.txt

輸出對象時,PowerShell會傳遞所有輸出對象到管道中的下一個指令。

将對象寫到控制台是把對象轉換為文本後寫到控制台,不會傳遞值到管道中,它通過Wrist-Host這個cmdlet實作。輸出對象和将對象寫到控制台看起來相似是因為每個管道會在最後隐式調用Out-Default,這個cmdlet處理所有輸入并将其寫到控制台。

1.5 作用域規則

函數會建立新的本地作用域,作用域繼承變量的可見性,函數可以讀取所有其作用域中及其父作用域中定義的變量。

對于命名對象,函數遵循類似變量的作用域規則。可以在任何作用域中聲明函數,函數會在其作用域及其子作用域有效,這意味着可以嵌套函數。下例是嵌套函數:

PS C:\> function OuterFunction()

>>         function InnerFunction()

>>             Write-Host "Printed by InnerFunction!"

>>         InnerFunction

PS C:\> OuterFunction

Printed by InnerFunction!

InnerFunction函數對于OuterFunction函數内部的代碼可見,這樣能有效地保護函數内部特定的邏輯不被外部通路。如果強行在OuterFunction函數外部作用域調用InnerFunction函數,将會報錯,如:

PS C:\> InnerFunction

The term 'InnerFunction' is not recognized as a cmdlet,

function, operable program, or script file.

Verify the term and

try again.

At line:1 char:14

+ InnerFunction <<<<

    + CategoryInfo          : ObjectNotFound:

(InnerFunction:String) [], CommandNotFoundException

    + FullyQualifiedErrorId : CommandNotFoundException

類似變量指派,在子作用域中的函數會覆寫父作用域中的同名函數,下例在目前作用域中覆寫父作用域中的同名函數:

>>        function Do-Something()

>>             Write-Host "Original Do-Something function"

>>          }

>>        function InnerFunction()

>>             function Do-Something()

>>             {

>>                 Write-Host "Overriden Do-Something function"

>>             }

>>             Do-Something

>>         Do-Something

Overriden Do-Something

Original Do-Something function

OuterFunction函數定義了Do-Something函數,其中嵌套了InnerFunction函數。該函數中定義了與父作用域中的Do-Something函數,覆寫了父作用域中的同名函數。這種覆寫隻在InnerFunction函數作用域中有效,一旦退出這個作用域,Do-Something傳回原值。

與Get-Variable不同,Get-Command不支援作用域參數。一旦在目前作用域中覆寫,則無法從父作用域中得到函數。

另外,可以在函數名前使用global、script、local或private作用域辨別符。下例在global和local作用域中聲明同名函數,然後用命名空間字首差別二者:

PS C:\> function global:Do-Something()

>>     Write-Host "Global Do-Something"

>> function InnerScope()

>>      function local:Do-Something()

>>      {

>>         Write-Host "Local Do-Something"

>>      }

>>     local:Do-Something

>>     global:Do-Something

PS C:\> InnerScope

Local Do-Something

Global Do-Something

用于變量的作用域命名空間字首對函數有效,如果要調用全局變量,則必須使用$global:myVariable文法。而函數的作用域命名空間不使用美元符,引用全局函數的文法格式是global:myFunction。

當在不同作用域級别重載本地函數後,隻要在目前作用域的函數被重載前獲得函數的引用,就可以從父作用域調用該函數,如下例:

>>     function Do-Something()

>>         Write-Host "Original Do-Something"

>>     function InnerFunction()

>>         $original = Get-Command Do-Something -type Function

>>         function Do-Something()

>>             Write-Host "Override Do-Something"

>>             Write-Host "Calll original function......"

>>             &$original

>>     InnerFunction

Override Do-Something

Calll original function......

Original Do-Something

2 過濾器

在函數中接收管道輸入需要在函數中定義begin、process和end段,當對象傳遞給函數時這些段就會執行。3個段中隻有process是必須的,它作用于傳遞的每個對象,通常在其中可以使用$_這個特殊變量引用目前對象。begin和end段分别在管道執行前後執行。下例為定義接收檔案的管道并計算檔案總大小的函數:

PS C:\> function Get-FileSize

>>     begin

>>         $total = 0

>>     process

>>         Write-Host "processing: $($_.name)"

>>         $total += $_.Length

>>     end

>>         return $total

PS C:\> dir *.txt | Get-FileSize

processing: digit.txt

processing: largetext.txt

processing: smalltext.txt

processing: test.txt

processing: test2.txt

19083

很多情況下,在處理管道輸入時僅需要定義process段,如過濾程序的集合并隻顯示啟動不超過5分鐘的程序等。為此需要定義Get-RecentlyStarted函數,在process段中傳回StartTime屬性值小于5分鐘的管道對象:

PS C:\> function Get-RecentlyStarted

>>         $start = $_.StartTime

>>         if ($start -ne $null)

>>             $now = [datetime]::Now

>>             $diff = $now - $start

>>             if ($diff.TotalMinutes -lt 5)

>>                 return $_

PS C:\> Get-Process | Get-RecentlyStarted

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- -----------

    291      10    10240      17836   101    19.38   3324 iexplore

     80       4     2740       7252    67     2.73   3400 mspaint

     45       2     1192       3952    54     1.09   1892 notepad

    234       8     3336       5072    51     2.28   2440 svchost

PowerShell不允許函數同時包含在調用時立即執行的任意語句,以及與管道相關的begin、process和end段。PowerShell正常函數和管道處理函數完全不同,管道函數模式經常用于過濾集合。為了便于定義過濾函數,可以引用fliter關鍵字。下例使用過濾器重新定義上一個執行個體:

PS C:\> filter Get-RecentlyStarted

>>     $start = $_.StartTime

>>     if ($start -ne $null)

>>         $now = [datetime]::Now

>>         $diff = $now - $Start

>>         if ($diff.TotalMinutes -lt 5)

>>             return $_

PS C:\> Get-Process |  Get-RecentlyStarted

    189       8     5452      11076    86     2.63   3368 iexplore

     80       4     2740       7228    67     1.58   2408 mspaint

    229       8     3308       5076    51     2.28   2440 svchost

     82       4     2920       8192    71     2.84   2892 wordpad

使用過濾器替代函數會清除嵌套的複雜度而使代碼更為簡潔且易讀,可以使用Get-Command檢視Get-RecentlyStarted的詳細資訊。PowerShell規定過濾器是函數的特例,因為使用Function和Filter類型查找指令都會傳回前面的過濾器,如:

PS C:\> Get-Command Get-RecentlyStarted -type Function

CommandType     Name                                                Definition

-----------     ----                                                ----------

Function        Get-RecentlyStarted                                 ...

PS C:\> Get-Command Get-RecentlyStarted -type Filter

上例中的Definition屬性被省略,可以用下面的方法擷取:

PS C:\> (Get-Command Get-RecentlyStarted -type Filter).ScriptBlock

    $start = $_.StartTime

    if ($start -ne $null)

    {

        $now = [datetime]::Now

        $diff = $now - $Start

        if ($diff.TotalMinutes -lt 5)

        {

            return $_

        }

    }

與管道配合工作的函數與過濾器看起來相似,盡管函數的process塊語義等同于過濾器,但是函數在内部以FunctionInfo對象存在;而過濾器以FilterInfo對象存在。

下例中的函數會過濾掉管道中所有的奇數,隻保留偶數:

PS C:\> function Even-Function

>>         if ($_ % 2 -eq 0)

>>             $_

下例使用filter實作同樣的邏輯:

PS C:\> filter Even-Filter

>>     if ($_ %2 -eq 0)

>>         $_

盡管Even-Function和Even-Filter在查詢函數類型的指令時均傳回,但是對于CommandType屬性,二者分别傳回Function和Filter,如:

PS C:\> Get-Command Even-Function -CommandType Function

CommandType    Name                        Definition

-----------         ----                          ----------

Function         Event-Function                process {...

PS C:\> Get-Command Even-Filter -CommandType Function

Filter              Even-Filter                   ...

另外一種在運作時差別函數和過濾器的方法是檢查内部ScriptBlock對象的IsFilter屬性,如:

PS C:\> $function = (Get-Command Event-Function -CommandType Function)

PS C:\> $function.ScriptBlock.IsFilter

False

PS C:\> $function = (Get-Command Even-Filter -CommandType Function)

True

需要再次重申,這兩種函數對象以完全相同的方式工作。

微軟MSDN位址:http://msdn.microsoft.com/zh-cn/ff943785.aspx

賽迪網位址:http://news.ccidnet.com/art/32857/20100617/2089211_1.html

作者: 付海軍

出處:http://fuhj02.blog.51cto.com

版權:本文版權歸作者和51cto共有

轉載:歡迎轉載,為了儲存作者的創作熱情,請按要求【轉載】,謝謝

要求:未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任

個人網站: http://txj.shell.tor.hu

繼續閱讀