Powershell学习笔记——函数和函数库

2018-06-09 09:57 更新

   前段时间公司封闭开发,就在封闭的前一天感冒发烧,为了封闭,一顿猛药下去,烧是退了,却在扁桃附近爆发出来——扁桃发炎加溃疡,搞了十多天才好啊,天天喝稀饭啊……所以请大家原谅这么久没有续上学习笔记。顺便:过两天继续封闭,所以这个笔记更新速度可能不会很快了,我尽力。

函数

  函数是Powershell里一个非常重要的东西,与CMD比较起来,这绝对是一个亮点。CMD中只能用“标签”+CALL来模拟函数,而Powershell不仅支持函数,还支持3种类型的函数:普通函数(Function)、过滤器(Filter)和管道函数(Pipeline Function)。除此之外,Powershell的参数解析也是非常智能和强大——当然,参数形式的约定是必不可少的部分。

  函数可以为一系列的操作提供一个快捷命令,而且还可以通过参数来改变函数内的流程或运算结果。还是先来一个示例:

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

  这里定义了一个名为“cd~”的函数,目的是直接回到用户目录,就像Linux的“cd ~”一样。这里function是定义函数的关键字,cd~是函数名,{}中的部分则是函数体。——这里还没用到参数,这个,后面再说。执行“cd~”,实际是执行了函数体里面的内容。

  当然,如果一个函数比较复杂,要一行写完是比较痛苦的。那么也可以分多行来写,比如

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

  当然,这样写不太方便,因为你在写到最后一行时如果发现前面有错,连修改的机会都没有。不过,如果把一段程序或者函数定义写在脚本文件中,再来执行脚本就会方便得多了——我想,关于脚本文件,在前面已经说过,这里就不用多说了。

函数的参数

  为了演示参数,我们用另一个例子。这个例子会将参数转为大写输出:

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

  这个示例中,用到了未命名参数数组$args。只要是没有命名的参数都会按顺便存在在这个数组中。如果是多个未命名参数,我们可以用用一个循环来依次处理。比如:

  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:\> 

  循环是控制流程的内容,之前的笔记还没提到。这篇笔记主要是记函数,所以也暂时略过不说。

命名参数

  不过这里有一个问题却不得不说——未命名参数。顾名思义,未命名参数就是没有名字的参数;而且,既然有未命名参数,就一定有命名参数。那么命名参数又是什么呢?还是看例子(这次的例子是写的脚本文件):

  1. # sample.ps1 
  2.   
  3. # 命名参数示例 
  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 # 参数值为按顺便赋予命名参数 
  14. hello -ismale false Jenny # 指定了名称的参数值会赋给对应的命名参数,其它的按顺序赋给其它命名参数   

  运行结果如下:

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

多余的参数

  上面的示例中有两个调用函数的示例。第一个没有为参数值指定名称,那么函数调用时会按顺序把参数值赋给参数列表中的命名参数;而第二种调用,为ismale参数指定了值,那么会先将指定了名称的参数值赋给相应的命名参数,其它的参数值再按顺序赋予其它命名参数。现在有一个问题:如果赋予所有命名参数之后还有参数传入,这些参数是否可以通过$args来访问呢?继续做实验,把上面的示例稍做改动:

  1. # sample.ps1 
  2.   
  3. # 多余的参数示例 
  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.   

  运行结果

  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 

默认参数值

  其实多做做实验,答案都是显而易见的。继续下一个问题:“[bool]::parse($isMale)”能不能简化?[bool]::parse其实是调用了System.Boolean的静态方法Parse来将字符串解析为布尔类型的值。如果我们直接传入布尔类型的参数不就可以简化了么?就像这样

  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 

  其实,还可以更简单,比如对于男士,省略第2个参数……当然,现在这个脚本不行,试试就知道了,可以省略$isMale参数的是女士。再做点改动:

  1. # sample.ps1 
  2.   
  3. # 默认参数值示例 
  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 

开关[switch]参数

  既然isMale是一个布尔,在控制台脚本里,会很容易让人想起“开关”,即通过一个参数是否存在来表示两种不同的参数值。比如,如果加了“-ismale”参数,则表示男士,否则表示女士——哦,应该换一下,因为不加参数为默认形式,而我们前面约定的默认情况是男士,所以开关参数应该改为“-isFemale”,不过既然是开关,那“is”也可以省了,就是“-female”。示例:

  1. # sample.ps1 
  2. # [switch]参数值示例 
  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 

  有注意到定义参数时指定的[switch]标记么?这叫参数类型。当然[switch]只是参数类型中的一种……

指定参数的类型

  除此之外,还可以为参数指定类型,这样的话,只要给予的参数值不是指定的类型,或者不能转换为指定的类型,就会抛出错误。当然,指定了类型的参数,在函数内进行处理时,往往可以活力掉类型转换的步骤,比如我们想把年、月、日3个参数拼成一个8位长度的日期字符串,下面哪个函数是可以完成呢?

  1. # sample.ps1 
  2.   
  3. # 指定类型的参数示例 
  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的三个参数都被当作int型进行处理,相加的结果是2042。而add2中,将$a申明为string类型,虽然$b和$c仍然是被当作int型进行处理,但是“+”遇到不同类型的运算时是自动转为其左边的类型进行运算,所以是字符串相连,结果20111021。

函数的返回值

  Powershell关于函数返回值这个问题,比较复杂。在其它脚本或者语言中,通常来说,通过return之类的关键返回的才是返回值,而Powershell不同,只有是输出到Output的内容,都是返回值,比如

  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函数返回了包含3个值的一个数组,除了最后的return外,前面两个都是写入管道的。哦,管道……这又是一个复杂的东西,后面再来复习。现在只需要记得write-output输出,直接字面值或者变量值输出以及return都会产生返回值就对了。

  很明显,Powershell的函数允许一次返回多个值,这些值都保存在一个数组中。当然,如果函数只返回一个值,那就不需要数组了,比如把上面的示例精简一下:

  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,过滤器函数

  除了定义函数之外,也可以定义过滤器。过滤器可以对通过管道进来的内容进行过滤,比如下面这个列子就是为了只列出.exe文件:

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

  运行结果:

  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进行处理。再看test函数的内容,管道中传入的每一项都由特殊变量$_引用。test对传入的每一项都进行判断,将扩展名为.exe的文件对象输出,其余的丢弃。

管道函数

  其实,过滤器是一种特殊的函数,管道函数的简化版。管道函数也是一种特殊的函数,它包含3个部分,begin、process和end。管道输出在进入管道函数的时候,会首先运行begin区域的脚本,仅运行一次;之后从管道进来的每个对象都会经历process过程;所有项结束之后,会触发end区域的脚本。而过滤器就是只定义了process区的管理函数。还是来看例子:

  1. # sample.ps1 
  2. function test() { 
  3.     begin 
  4.         "处理开始了" 
  5.     } 
  6.     process 
  7.         if ($_ -like "a*") { $_ }  
  8.     } 
  9.     end 
  10.         "处理完成了" 
  11.     } 
  12.  } 
  13.   
  14. # 这次示例用一个数组来演示 
  15.  $("a", "ab", "bac", "b", "bc", "ac") | test 

  输出:

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

  管道函数的3个块都可以省略,包括process块,只不过如果省略了process块之后,这个函数就没啥意义了。不过根据实际情况,begin和end块倒是经常被省略的。

函数库

  前面关于运算符的笔记中提到了点号(.)运算符可以用于引入一个脚本,而这个脚本就类似于C/C++中#include的方式被引入到当前位置并执行。那么,如果这个脚本里面只包含函数定义,而不包含其它内容,那么这个脚本就是一个函数库。每次使用该函数库的时候,只需要使用点号运算符引入即可。比如上面的例改稍作改动:

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

  然后在Powershell控制台运行:

  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> 

  如果有兴趣试试不使用点号,而是直接运行脚本,那么第二条命令就会出错是,因为test未定义。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号