函数
简单来说,函数就是命名的语句块。
简单函数定义语法如下:
1 | Function <function-name> ($param1, $param2) |
完整的函数结构包括:关键字、作用域、函数名、参数、函数体。完整语法参考 这里。
关键字
函数定义通常以 Function
开头,说明定义类型为函数。
关键字还可以是
Filter
,定义的是过滤器,它是一种特殊的函数。
作用域
通常,函数属于创建它的作用域,除非有必要否则一般由 PowerShell 自行管理其作用域。
如需指定作用域,需在函数名前加作用域前缀:
1 | function [<scope-modifier>:]<name> {<function-body>} |
函数名
PowerShell 建议的函数名格式为“动名词对(verb-noun pair)”,并且动词还规定有标准的,可通过命名 Get-Verb
查看。
函数名理论上可以是“任意”的,但符合 PowerShell 建议格式的函数名更易被他人理解。
参数
定义方式
PowerShell 有两种参数定义方式,一种跟大多数语言类似,将参数放置于函数名后的圆括号中,如:
1 | function test($a, $b) { |
也可以将参数定义在函数体内,置于 param
语句中,如:
1 | function test1 { |
效果上是相同的,但 PowerShell 推荐后者。
命名参数与位置参数
如果在定义函数时有声明参数,显然这些参数是有名字的,因此,在调用该函数时可以用“名-值对”的形式传递该参数。此时,称为“命名参数”。
另一方面,也允许定义函数时不声明参数,而在执行时传递参数,这种参数没有名称,只能通过相对位置访问,称为“位置参数”。
不过,即使是声明的参数,默认情况下也会绑定位置。因此,也可以不使用其名称传参,此时,也是位置参数。
可见,命名参数和位置参数是依调用情况而不是定义情况而定的。下文会进一步讨论。
参数类型
到目前为止,都还没有限制参数的类型——这在 PowerShell 中是允许的——如果不指定参数类型,那么理论上,可以传入任何值。但实际上,这通常不是我们想要的。
要为参数指定类型需要在定义函数时,在参数之前加上类型限制,比如:[int]$num
、[string]$name
等等。这跟限制变量类型一致。
1 | function Add([int]$one, [int]$another) |
开关参数
对于 PowerShell 而言,一个布尔类型的参数,更合适的作法是将其定义为 [switch]
类型,称为“开关参数”。
开关参数不要求传值,如果为 $true
只需要列出该参数,为 $false
不列出。
默认值
命名参数可以指定默认值,只需要在参数声明时,后跟 =
将默认值赋值给参数即可。如:
1 | function Add([int]$one=1, [int]$another=1) |
需要注意的是,默认值不能与必需参数(Mandatory)一起使用。一旦指定为必需参数,默认值就将失效。
想要一个参数既有默认值,又是必需的(即传参“非空”),可以使用 ValidateNotNullOrEmpty
参数验证属性:
1 | function Test-ParameterValidation { |
一个带有技巧性的惯用法是,为参数指定一个异常默认值。
1
2
3 function doNotMiss($arg=$(throw "请提供参数 arg!")) {
$arg
}这样,如果不为
arg
参数传值,那么将得到指定的异常提示。注意这种参数与必需参数在缺少时的行为差异。
函数体
访问所有参数
通过自动变量 $args
数组,可以访问所有未声明的参数。
1 | function sum |
上面的函数并没有声明任何参数,但在调用时传入了参数,并在函数体中通过 $args
访问它们。
如果函数调用时,所传参数都声明了,那么,可以使用自动变量 $PSBoundParameters
访问,它是一个字典,以参数名为键,传入的参数值为值。
1 | function print($a, $b) |
$args
包含函数未声明的参数,而 $PSBoundParameters
包含函数声明的参数。因此,如果传递的参数既有命名参数又有位置参数,那么,两个变量合在一起才能访问所有参数。
显然,
$args
和$PSBoundParameters
是互补关系,不存在交集。
管道参数
函数可以接收并处理管道对象,不过其函数体结构略有不同,语法如下:
1 | function pipingFunction |
Begin
和 End
块中的语句分别在开始和结束时执行一次,Process
块中的语句针对管道中每一个对象执行一次。这 3 个块都是可选,如果都没有,相当于所有语句都位于 End
块中。
1 | function printPipe |
Process
块中使用 $_
访问当前处理的管道对象。
管道参数还涉及一个自动变量 $input
。在开始时(Begin
块中)它是空的。如果没有 Process
块,那么管道对象会逐一追加到其中,因此,在结束时(End
块中)它包含所有管道对象;但是如果有 Process
块,管道对象将从 $input
移到 $_
中,因此,结束时(End
块中)它是空的。
上文提到过过滤器(Filter),它的效果类似于一个仅有
Process
块的函数。
返回值
PowerShell 比较特殊的一点是,会将函数中所有的“输出”作为返回值。
这里的“输出”指的是除写控制台操作外,执行后会在控制台回显值的语句。
如果不想某些输出成为返回值,可以用写入控制台的方式避免。
函数的返回值可以不是单值,当有多个输出时,它们将被自动收集到一个数组中返回。
return
语句可以返回值,并且它将阻止后续语句执行。
调用
PowerShell 调用函数跟执行命令格式类似:
1 | PS > function-name -arg1 value1 others |
传递数组
如果某个参数是数组,则各元素用逗号分隔即可。
1 | PS > function-name -arrayArg e1,e2,e3 |
Splatting
Splatting 是一种参数传递方式,它可将一系列参数传入命令,而不需要一一将其列在命令之后。以官方示例来说明:
1 | function Get-MyCommand { Get-Command @Args } |
@Args
代表的是 $Args
,即 Get-MyCommand
函数将调用它的所有未声明参数都传给了 Get-Command
函数进行了调用。
同理,也可以使用 @PSBoundParameters
代表 $PSBoundParameters
。
更一般地,可以使用 @<HashTable>
和 @<Array>
分别传递哈希表和数组表示的一系列参数。
参数截断
调用函数时,命名参数的名称是可以截断的,只要无歧义即可。换句话说,调用函数时指定参数名可以不写参数全名,而只写部分前缀,前提是通过这个前缀能唯一确定一个参数。
参数截断应当被视为一种命令行快速编写方式,不应在脚本中使用,这会降低脚本的可读性。
管理
我们可以通过 Function:
驱动器查看函数定义:
1 | # 查看函数定义 |
同理,我们可以借此删除函数:
1 | del Function:FunctionName |
高级函数
所谓高级函数,与普通函数相比,具有多个自动添加到函数的通用参数,包括 Verbose
和 Debug
。
它使用 CmdletBinding
属性标识。
1 | function Test-MrCmdletBinding { |
高级参数
高级函数中,可以为参数添加参数属性(parameter attribute)及属性参数(argument)来限制参数值。语法如下:
1 | Param( |
下面以 Parameter
属性为例说明,因为它是所有参数属性中属性参数最多的。
注意这里有两个“参数”容易混淆——这是中文翻译的问题—— parameter 和 argument 通常均翻译为“参数”,但在这里,parameter 特指函数参数,而 argument 指参数属性(parameter attribute)的参数。
更多参数属性可参考 这里。
Mandatory
属性参数指定参数是否必需,默认值为 $false
,即参数可选;设置为 $true
则必需,如缺省该参数则提示输入。
1 | [Parameter(Mandatory=$true)] |
Position
属性参数用以指定参数位置索引号,从 0 开始。
ParameterSetName
属性参数指定参数所属参数集的名称。所谓“参数集”,就是参数分组,这使得传入不同的参数执行不同函数逻辑成为可能。 为使参数集唯一可确定,每个参数集都应该至少有一个其他参数集没有的参数。如果一个参数不指定参数集,则属于所有参数集。如果一个参数属于多个但不是全部参数集,则需要在多个 Parameter
参数属性中定义。
可使用
$PSCmdlet.ParameterSetName
获取当前使用的参数集。
CmdletBinding属性
DefaultParameterSetName
定义默认使用的参数集名称。当所传参数不足以明确参数集时造成冲突,默认参数集名称指定此时使用哪个参数集。
PositionalBinding
定义声明的参数是否绑定位置。默认值为 $true
,会为声明的参数绑定位置,因此调用时不一定需要使用命名参数。
SupportsShouldProcess
添加 WhatIf
和 Confirm
参数,仅修改命令需要这些参数。
脚本
脚本是写在 .ps1
后缀文件中的 PowerShell 程序,在命令行中执行脚本中的命令和执行脚本几乎没有差别。
但是,脚本中的变量、函数等默认加载到 Script 作用域,脚本执行完成后,脚本作用域会删除,换句话说,脚本中的变量、函数等也会随之删除。如果想要在脚本执行后,还能使用脚本中的变量、函数等,则需要将其加载到 Global 作用域中。
这就要对脚本执行“dot-source”操作,即在脚本前加 .
(英文句点)+ 空格:
1 | . ./MyScript.ps1 |
关于注释
尽量不使用内联注释,因为除非查看脚本源码,否则是不可见的。更好的选择是使用 Write-Verbose
,并使用 Verbose
参数调用,否则不会显示详细输出。
讨论
简单函数 vs. 高级函数
最显明的,简单函数不具有 CmdletBinding
属性的配置能力,不能使用高级参数。
高级函数将拥有与 cmdlet 类似的能力,其中之一就是支持常见参数(Common Parameter),而简单函数不支持。
必需参数
上文提到了两种定义必需参数的方法,一种是为参数指定抛出异常的默认值,另一种是使用 Mandatory
参数。
注意两种必需参数在缺少该参数调用时,行为是不一样的。抛异常的方式会使脚本停止,而 Mandatory
参数的方式会提示用户输入参数值。
参考
函数语法
1 | function [<scope:>]<name> [([type]$parameter1[,[type]$parameter2])] |
CmdletBinding属性语法
1 | { |
链接
MS PS Doc - About Functions Advanced
MS PS Doc - About Functions Advanced Parameters