Powershell學(xué)習(xí)筆記——函數(shù)和函數(shù)庫

2018-06-09 09:57 更新

   前段時(shí)間公司封閉開發(fā),就在封閉的前一天感冒發(fā)燒,為了封閉,一頓猛藥下去,燒是退了,卻在扁桃附近爆發(fā)出來——扁桃發(fā)炎加潰瘍,搞了十多天才好啊,天天喝稀飯啊……所以請大家原諒這么久沒有續(xù)上學(xué)習(xí)筆記。順便:過兩天繼續(xù)封閉,所以這個(gè)筆記更新速度可能不會很快了,我盡力。

函數(shù)

  函數(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è)示例:

  1. PS C:\Users\james> f: 
  2. PS F:\> function cd~ { cd c:\users\james } 
  3. PS F:\> cd~ 
  4. 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ù)雜,要一行寫完是比較痛苦的。那么也可以分多行來寫,比如

  1. PS C:\users\james> function cd~ { 
  2. >> cd c:\users\james 
  3. >> } 
  4. >> 
  5. PS C:\users\james> 

  當(dāng)然,這樣寫不太方便,因?yàn)槟阍趯懙阶詈笠恍袝r(shí)如果發(fā)現(xiàn)前面有錯(cuò),連修改的機(jī)會都沒有。不過,如果把一段程序或者函數(shù)定義寫在腳本文件中,再來執(zhí)行腳本就會方便得多了——我想,關(guān)于腳本文件,在前面已經(jīng)說過,這里就不用多說了。

函數(shù)的參數(shù)

  為了演示參數(shù),我們用另一個(gè)例子。這個(gè)例子會將參數(shù)轉(zhuǎn)為大寫輸出:

  1. PS F:\> function toUpper { $args[0].toUpper() } 
  2. PS F:\> toUpper "james" 
  3. JAMES 
  4. PS F:\> 

  這個(gè)示例中,用到了未命名參數(shù)數(shù)組$args。只要是沒有命名的參數(shù)都會按順便存在在這個(gè)數(shù)組中。如果是多個(gè)未命名參數(shù),我們可以用用一個(gè)循環(huán)來依次處理。比如:

  1. PS F:\> function toUpper { 
  2. >> foreach ($a in $args) { $a.toUpper() } 
  3. >> 
  4. >> 
  5. PS F:\> toupper hello james fancy 
  6. HELLO 
  7. JAMES 
  8. FANCY 
  9. PS F:\> 

  循環(huán)是控制流程的內(nèi)容,之前的筆記還沒提到。這篇筆記主要是記函數(shù),所以也暫時(shí)略過不說。

命名參數(shù)

  不過這里有一個(gè)問題卻不得不說——未命名參數(shù)。顧名思義,未命名參數(shù)就是沒有名字的參數(shù);而且,既然有未命名參數(shù),就一定有命名參數(shù)。那么命名參數(shù)又是什么呢?還是看例子(這次的例子是寫的腳本文件):

  1. # sample.ps1 
  2.   
  3. # 命名參數(shù)示例 
  4.   
  5. function hello($name, $isMale) { 
  6.      if ([bool]::parse($isMale)) { 
  7.          "Hello Mr. $name" 
  8.      } else { 
  9.          "Hello Ms. $name" 
  10.      } 
  11.  } 
  12.   
  13. hello James true # 參數(shù)值為按順便賦予命名參數(shù) 
  14. hello -ismale false Jenny # 指定了名稱的參數(shù)值會賦給對應(yīng)的命名參數(shù),其它的按順序賦給其它命名參數(shù)   

  運(yùn)行結(jié)果如下:

  1. PS F:\james\Desktop> .\sample.ps1 
  2. Hello Mr. James 
  3. Hello Ms. Jenny 

多余的參數(shù)

  上面的示例中有兩個(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):

  1. # sample.ps1 
  2.   
  3. # 多余的參數(shù)示例 
  4.   
  5. function hello($name, $isMale) { 
  6.     if ([bool]::parse($isMale)) { 
  7.         "Hello Mr. $name" 
  8.     } else { 
  9.         "Hello Ms. $name" 
  10.     } 
  11.       
  12.     foreach ($a in $args) { 
  13.         "MORE ARG: $a" 
  14.     } 
  15.  } 
  16.   
  17. hello James true a b c d e f g 
  18.   

  運(yùn)行結(jié)果

  1. PS F:\james\Desktop> .\sample.ps1 
  2. Hello Mr. James 
  3. MORE ARG: a 
  4. MORE ARG: b 
  5. MORE ARG: c 
  6. MORE ARG: d 
  7. MORE ARG: e 
  8. MORE ARG: f 
  9. MORE ARG: g 

默認(rèn)參數(shù)值

  其實(shí)多做做實(shí)驗(yàn),答案都是顯而易見的。繼續(xù)下一個(gè)問題:“[bool]::parse($isMale)”能不能簡化?[bool]::parse其實(shí)是調(diào)用了System.Boolean的靜態(tài)方法Parse來將字符串解析為布爾類型的值。如果我們直接傳入布爾類型的參數(shù)不就可以簡化了么?就像這樣

  1. # sample.ps1 
  2.   
  3. function hello($name, $isMale) { 
  4.      if ($isMale) { 
  5.          "Hello Mr. $name" 
  6.      } else { 
  7.          "Hello Ms. $name" 
  8.      } 
  9.  } 
  10.   
  11. hello James $true 
  12.  hello -ismale $false Jenny 

  其實(shí),還可以更簡單,比如對于男士,省略第2個(gè)參數(shù)……當(dāng)然,現(xiàn)在這個(gè)腳本不行,試試就知道了,可以省略$isMale參數(shù)的是女士。再做點(diǎn)改動(dòng):

  1. # sample.ps1 
  2.   
  3. # 默認(rèn)參數(shù)值示例 
  4.   
  5. function hello($name, $isMale=$true) { 
  6.      if ($isMale) { 
  7.          "Hello Mr. $name" 
  8.      } else { 
  9.          "Hello Ms. $name" 
  10.      } 
  11.  } 
  12.   
  13. hello James 
  14.   
  15. # Hello Mr. James 

開關(guān)[switch]參數(shù)

  既然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”。示例:

  1. # sample.ps1 
  2. # [switch]參數(shù)值示例 
  3. function hello($name, [switch] $female) { 
  4.     if (!$female) { 
  5.         "Hello Mr. $name" 
  6.     } else { 
  7.         "Hello Ms. $name" 
  8.     } 
  9.  
  10. hello James 
  11. hello Jessy -female 
  12.  
  13. # Hello Mr. James 

  有注意到定義參數(shù)時(shí)指定的[switch]標(biāo)記么?這叫參數(shù)類型。當(dāng)然[switch]只是參數(shù)類型中的一種……

指定參數(shù)的類型

  除此之外,還可以為參數(shù)指定類型,這樣的話,只要給予的參數(shù)值不是指定的類型,或者不能轉(zhuǎn)換為指定的類型,就會拋出錯(cuò)誤。當(dāng)然,指定了類型的參數(shù),在函數(shù)內(nèi)進(jìn)行處理時(shí),往往可以活力掉類型轉(zhuǎn)換的步驟,比如我們想把年、月、日3個(gè)參數(shù)拼成一個(gè)8位長度的日期字符串,下面哪個(gè)函數(shù)是可以完成呢?

  1. # sample.ps1 
  2.   
  3. # 指定類型的參數(shù)示例 
  4.   
  5. function add1($a, $b, $c) { 
  6.      $a + $b + $c 
  7.  } 
  8.   
  9. function add2([string]$a, $b, $c) { 
  10.      $a + $b + $c 
  11.  } 
  12.   
  13. add1 2010 10 21 # 輸出:2042 
  14.  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。

函數(shù)的返回值

  Powershell關(guān)于函數(shù)返回值這個(gè)問題,比較復(fù)雜。在其它腳本或者語言中,通常來說,通過return之類的關(guān)鍵返回的才是返回值,而Powershell不同,只有是輸出到Output的內(nèi)容,都是返回值,比如

  1. # sample.ps1 
  2.  function test() { 
  3.       write-output "Hello" 
  4.       "James Fancy" 
  5.       return "OK" 
  6.  } 
  7.  $a = test 
  8.  $a.GetType().FullName 
  9.  $a 

  它的輸出:

  1. PS F:\james\Desktop> .\sample.ps1 
  2. System.Object[] 
  3. Hello 
  4. James Fancy 
  5. 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ù)組了,比如把上面的示例精簡一下:

  1. # sample.ps1 
  2.  function test() { 
  3.       "James Fancy" 
  4.  } 
  5.  $a = test 
  6.  $a.GetType().FullName # 輸出:System.String 
  7.  $a # 輸出:James Fancy 
  8.   

Filter,過濾器函數(shù)

  除了定義函數(shù)之外,也可以定義過濾器。過濾器可以對通過管道進(jìn)來的內(nèi)容進(jìn)行過濾,比如下面這個(gè)列子就是為了只列出.exe文件:

  1. # sample.ps1 
  2. filter test() { 
  3.     # 只列出.exe擴(kuò)展名的文件 
  4.     if ($_.extension -eq ".exe") { $_ }  
  5.   
  6. dir | test 

  運(yùn)行結(jié)果:

  1. PS E:\james\Desktop> cd c:/windows  
  2. PS C:\windows> E:\james\Desktop\sample.ps1  
  3.  
  4. 目錄: C:\windows 
  5.  
  6.  
  7. Mode LastWriteTime Length Name 
  8. ---- ------------- ------ ---- 
  9. -a--- 2009-7-14 9:14 65024 bfsvc.exe 
  10. -a--- 2011-6-9 11:10 642240 bjzq.exe 
  11. -a--- 2010-1-31 15:23 2614272 explorer.exe 
  12. -a--- 2009-7-14 9:14 13824 fveupdate.exe 
  13. -a--- 2009-7-14 9:14 497152 HelpPane.exe 
  14. -a--- 2009-7-14 9:14 15360 hh.exe 
  15. -a--- 2011-3-21 23:21 78848 KMSEmulator.exe 
  16. -a--- 2011-3-21 23:26 151552 KMService.exe 
  17. -a--- 2009-11-28 7:52 179712 notepad.exe 
  18. -a--- 2009-7-14 9:14 398336 regedit.exe 
  19. -a--- 2009-6-11 5:41 49680 twunk_16.exe 
  20. -a--- 2009-7-14 9:14 31232 twunk_32.exe 
  21. -a--- 2009-6-11 5:42 256192 winhelp.exe 
  22. -a--- 2009-7-14 9:14 9728 winhlp32.exe 
  23. -a--- 2009-7-14 9:14 9216 write.exe 
  24. -a--- 2011-8-10 12:27 34512 xinstaller.exe 
  25.  
  26.  
  27. 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ù)也是一種特殊的函數(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ù)。還是來看例子:

  1. # sample.ps1 
  2. function test() { 
  3.     begin 
  4.         "處理開始了" 
  5.     } 
  6.     process 
  7.         if ($_ -like "a*") { $_ }  
  8.     } 
  9.     end 
  10.         "處理完成了" 
  11.     } 
  12.  } 
  13.   
  14. # 這次示例用一個(gè)數(shù)組來演示 
  15.  $("a", "ab", "bac", "b", "bc", "ac") | test 

  輸出:

  1. PS E:\james\desktop> E:\james\Desktop\sample.ps1 
  2. 處理開始了 
  3. ab 
  4. ac 
  5. 處理完成了 

  管道函數(shù)的3個(gè)塊都可以省略,包括process塊,只不過如果省略了process塊之后,這個(gè)函數(shù)就沒啥意義了。不過根據(jù)實(shí)際情況,begin和end塊倒是經(jīng)常被省略的。

函數(shù)庫

  前面關(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):

  1. # sample.ps1 
  2. function test() { 
  3.     process { 
  4.         if ($_ -like "a*") { $_ }  
  5.   } 
  6. }

  然后在Powershell控制臺運(yùn)行:

  1. PS E:\james\desktop> . .\sample.ps1 
  2. PS E:\james\desktop> $("a", "ab", "bac", "b", "bc", "ac") | test 
  3. ab 
  4. ac 
  5. PS E:\james\desktop> 

  如果有興趣試試不使用點(diǎn)號,而是直接運(yùn)行腳本,那么第二條命令就會出錯(cuò)是,因?yàn)閠est未定義。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號