8. 语句
编辑说明
重要
Windows PowerShell 语言规范 3.0 于 2012 年 12 月发布,基于 Windows PowerShell 3.0。 此规范不反映 PowerShell 的当前状态。 没有计划更新本文档以反映当前状态。 此处提供了本文档供历史参考。
规范文档可从 Microsoft 下载中心以 Microsoft Word 文档的形式获取,网址为:https://www.microsoft.com/download/details.aspx?id=36389 该 Word 文档已转换为 Microsoft Learn 上的演示文稿。 转换期间,进行了一些编辑更改,以适应 Docs 平台的格式设置。 已更正某些拼写错误和次要错误。
8.1 语句块和列表
语法:
提示
语法定义中的 ~opt~
表示法指示词法实体在语法中是可选的。
statement-block:
new-lines~opt~ { statement-list~opt~ new-lines~opt~ }
statement-list:
statement
statement-list statement
statement:
if-statement
label~opt~ labeled-statement
function-statement
flow-control-statement statement-terminator
trap-statement
try-statement
data-statement
inlinescript-statement
parallel-statement
sequence-statement
pipeline statement-terminator
statement-terminator:
;
new-line-character
描述:
语句 指定要执行的操作。 除非此条款中另有说明,否则语句按词法顺序执行。
语句块 允许将一组语句分组到单个语法单元中。
8.1.1 标记语句
语法:
labeled-statement:
switch-statement
foreach-statement
for-statement
while-statement
do-statement
描述:
迭代语句 (§8.4) 或 switch 语句 (§8.6) 可以有选择性地紧靠在一个语句标签 label 之前。 语句标签用作中断(§8.5.1)或继续(§8.5.2) 语句的可选目标。 但是,标签不会更改控制流。
不允许在冒号 (:
) 和其后面的符号之间添加空格。
例子:
:go_here while ($j -le 100) {
# ...
}
:labelA
for ($i = 1; $i -le 5; ++$i) {
:labelB
for ($j = 1; $j -le 3; ++$j) {
:labelC
for ($k = 1; $k -le 2; ++$k) {
# ...
}
}
}
8.1.2 语句值
语句的值是它写入管道的累计值集合。 如果语句写入单个标量值,则表示该语句的值。 如果语句写入多个值,那么语句的值是存储在无约束的一个维数组元素中的值集合,并且按它们被写入的顺序排列。 请考虑以下示例:
$v = for ($i = 10; $i -le 5; ++$i) { }
循环没有执行任何迭代,没有任何信息写入管道。 语句的值是 $null
。
$v = for ($i = 1; $i -le 5; ++$i) { }
尽管循环迭代了五次,但没有任何信息写入管道。 语句的值为 $null。
$v = for ($i = 1; $i -le 5; ++$i) { $i }
循环迭代了五次,每次都将 int
value $i
值写入管道。 语句的值是长度为 5 的 object[]
。
$v = for ($i = 1; $i -le 5; ) { ++$i }
尽管循环迭代了五次,但没有任何信息写入管道。 语句的值是 $null
。
$v = for ($i = 1; $i -le 5; ) { (++$i) }
循环迭代了五次,每次都将值写入管道。 语句的值是长度为 5 的 object[]
。
$i = 1; $v = while ($i++ -lt 2) { $i }
循环迭代了一次。 语句的值是值为 2 的 int
。
下面是一些其他示例:
# if $count is not currently defined then define it with int value 10
$count = if ($count -eq $null) { 10 } else { $count }
$i = 1
$v = while ($i -le 5) {
$i # $i is written to the pipeline
if ($i -band 1) {
"odd" # conditionally written to the pipeline
}
++$i # not written to the pipeline
}
# $v is object[], Length 8, value 1,"odd",2,3,"odd",4,5,"odd"
8.2 Pipeline 语句
语法:
pipeline:
assignment-expression
expression redirections~opt~ pipeline-tail~opt~
command verbatim-command-argument~opt~ pipeline-tail~opt~
assignment-expression:
expression assignment-operator statement
pipeline-tail:
| new-lines~opt~ command
| new-lines~opt~ command pipeline-tail
command:
command-name command-elements~opt~
command-invocation-operator command-module~opt~ command-name-expr command-elements~opt~
command-invocation-operator: one of
& .
command-module:
primary-expression
command-name:
generic-token
generic-token-with-subexpr
generic-token-with-subexpr:
No whitespace is allowed between ) and command-name.
generic-token-with-subexpr-start statement-list~opt~ )
command-namecommand-name-expr:
command-name
primary-expressioncommand-elements:
command-element
command-elements command-element
command-element:
command-parameter
command-argument
redirection
command-argument:
command-name-expr
verbatim-command-argument:
--% verbatim-command-argument-chars
描述:
§7.12中讨论了 重定向;§7.11中讨论了 赋值表达式;§3.5.5中讨论了 命令调用操作符 点(.
)。 有关命令调用中的自变量到参数映射的讨论,请参阅 §8.14。
管道 中的第一个命令是表达式或命令调用。 通常,命令调用以 命令名称开头,该名称通常是一个裸标识符。 命令元素 表示命令的参数列表。 换行符或 n 非转义分号将终止管道。
命令调用由命令的名称后跟零个或多个参数组成。 控制参数的规则如下所示:
如果参数不是表达式,但包含任意文本且不带非转义空格,则被视为带有双引号。 保留字母大小写。
变量替换和子表达式扩展 (§2.3.5.2) 发生在 expandable-string-literals 和 expandable-here-string-literals 内。
引号中的文本允许将前导、尾随和嵌入的空格包含在参数的值中。 [注意:引用参数中存在空格不会将单个参数转换为多个参数。 尾注]
在参数周围加上括号会使表达式被计算,并传递其结果,而不是传递原始表达式的文本。
若要传递一个类似于 switch 参数(§2.3.4)但不是这样的参数,请将该参数括在引号中。
当指定与具有
[switch]
类型约束的参数匹配的参数(§8.10.5),参数名称的存在本身会导致该参数设置为$true
。 但是,可以通过向参数追加后缀来显式设置参数的值。 例如,给定类型约束参数 p,-p:$true
参数将 p 设置为 True,而-p:$false
将 p 设置为 False。--
的参数指示其后面的所有参数都将以实际形式传递,就像在它们周围放置了双引号一样。--%
的参数指示其后面的所有参数都将以最小的分析和处理进行传递。 此参数称为逐字参数。 谓词参数后面的参数不是 PowerShell 表达式,即使它们是语法有效的 PowerShell 表达式。
如果命令类型为 Application,则参数 --%
不会传递给命令。 --%
后面的参数会扩展任何环境变量(被 %
包围的字符串)。 例如:
echoargs.exe --% "%path%" # %path% is replaced with the value $env:path
未指定参数的计算顺序。
有关参数绑定的信息,请参阅 §8.14。 有关名称查找的信息,请参阅 §3.8。
参数处理完成后,将调用该命令。 如果调用的命令正常终止(§8.5.4),则控件将在命令调用后立即还原到脚本或函数中的点。 有关异常终止行为的说明,请参阅 break
(§8.5.1)、continue
(§8.5.2)、throw
(§8.5.3)、exit
(§8.5.5)、try
(§8.7)和 trap
(§8.8)。
通常,使用后跟任何参数的命令名称来调用命令。 但是,可以使用命令调用运算符 &。 如果命令名称包含非转义空格,则必须将其加引号并使用此运算符来调用。 由于脚本块没有名称,因此必须使用此运算符调用它。 例如,命令调用 Get-Factorial
的以下调用等效:
Get-Factorial 5
& Get-Factorial 5
& "Get-Factorial" 5
允许直接和间接递归函数调用。 例如,
function Get-Power([int]$x, [int]$y) {
if ($y -gt 0) { return $x * (Get-Power $x (--$y)) }
else { return 1 }
}
例子:
New-Object 'int[,]' 3,2
New-Object -ArgumentList 3,2 -TypeName 'int[,]'
dir e:\PowerShell\Scripts\*statement*.ps1 | Foreach-Object {$_.Length}
dir e:\PowerShell\Scripts\*.ps1 | Select-String -List "catch" | Format-Table path,linenumber -AutoSize
8.3 if 语句
语法:
if-statement:
if new-lines~opt~ ( new-lines~opt~ pipeline new-lines~opt~ ) statement-block
elseif-clauses~opt~ else-clause~opt~
elseif-clauses:
elseif-clause
elseif-clauses elseif-clause
elseif-clause:
new-lines~opt~ elseif new-lines~opt~ ( new-lines~opt~ pipeline new-lines~opt~ ) statement-block
else-clause:
new-lines~opt~ else statement-block
描述:
pipeline 控制表达式必须具有布尔类型或可隐式转换为该类型。 else-clause 为可选项。 可能存在零个或多个 elseif-clause。
如果顶层 pipeline 测试的结果是 True,则执行其 statement-block,然后语句执行将终止。 否则,如果存在 elseif-clause,并且其 pipeline 测试结果为 True,则会执行其 statement-block,并且语句执行将终止。 否则,如果存在 else-clause,则执行其 statement-block。
例子:
$grade = 92
if ($grade -ge 90) { "Grade A" }
elseif ($grade -ge 80) { "Grade B" }
elseif ($grade -ge 70) { "Grade C" }
elseif ($grade -ge 60) { "Grade D" }
else { "Grade F" }
8.4 迭代语句
8.4.1 while 语句
语法:
while-statement:
while new-lines~opt~ ( new-lines~opt~ while-condition new-lines~opt~ ) statement-block
while-condition:
new-lines~opt~ pipeline
描述:
控制表达式 while-condition 必须具有布尔类型或可隐式转换为该类型。 循环正文由 statement-block 组成,会被重复执行,直到控制表达式的测试结果为 False。 在每次执行循环正文之前,都会计算控制表达式。
例子:
$i = 1
while ($i -le 5) { # loop 5 times
"{0,1}`t{1,2}" -f $i, ($i*$i)
++$i
}
8.4.2 do 语句
语法:
do-statement:
do statement-block new-lines~opt~ while new-lines~opt~ ( while-condition new-lines~opt~ )
do statement-block new-lines~opt~ until new-lines~opt~ ( while-condition new-lines~opt~ )
while-condition:
new-lines~opt~ pipeline
描述:
控制表达式 while-condition 必须具有布尔类型或可隐式转换为该类型。 在 while 窗体中,循环正文由 statement-block 组成,会被重复执行,直到控制表达式的测试结果为 True。 在 until 窗体中,循环正文会被重复执行,直到控制表达式的测试结果为 True。 在每次执行循环体之后,对控制表达式进行求值。
例子:
$i = 1
do {
"{0,1}`t{1,2}" -f $i, ($i * $i)
}
while (++$i -le 5) # loop 5 times
$i = 1
do {
"{0,1}`t{1,2}" -f $i, ($i * $i)
}
until (++$i -gt 5) # loop 5 times
8.4.3 for 语句
语法:
for-statement:
for new-lines~opt~ (
new-lines~opt~ for-initializer~opt~ statement-terminator
new-lines~opt~ for-condition~opt~ statement-terminator
new-lines~opt~ for-iterator~opt~
new-lines~opt~ ) statement-block
for new-lines~opt~ (
new-lines~opt~ for-initializer~opt~ statement-terminator
new-lines~opt~ for-condition~opt~
new-lines~opt~ ) statement-block
for new-lines~opt~ (
new-lines~opt~ for-initializer~opt~
new-lines~opt~ ) statement-block
for-initializer:
pipeline
for-condition:
pipeline
for-iterator:
pipeline
描述:
控制表达式 在条件 中必须具有布尔类型或能够隐式转换为该类型。 循环正文由 statement-block 组成,会在控制表达式的测试结果为 True 时被重复执行。 在每次执行循环正文之前,都会计算控制表达式。
系统会在第一次计算控制表达式之前计算 for-initializer 表达式。 系统会计算表达式 for-initializer 以仅确定其副作用,所生成的任何值都会被丢弃,不会写入管道。
每次执行循环正文后计算 for-iterator 表达式。 系统会计算表达式 for-iterator 以仅确定其副作用,所生成的任何值都会被丢弃,不会写入管道。
如果省略了 for-condition 表达式,则此控制表达式的测试结果为 True。
例子:
for ($i = 5; $i -ge 1; --$i) { # loop 5 times
"{0,1}`t{1,2}" -f $i, ($i * $i)
}
$i = 5
for (; $i -ge 1; ) { # equivalent behavior
"{0,1}`t{1,2}" -f $i, ($i * $i)
--$i
}
8.4.4 foreach 语句
语法:
foreach-statement:
foreach new-lines~opt~ foreach-parameter~opt~ new-lines~opt~
( new-lines~opt~ variable new-lines~opt~ *in* new-lines~opt~ pipeline
new-lines~opt~ ) statement-block
foreach-parameter:
-parallel
描述:
循环正文由 statement-block 组成,系统会针对 pipeline 指定的集合中 variable 变量所指定的每个元素执行此循环正文。 变量 的范围不限于 foreach 语句。 因此,在循环正文完成执行后,它会保留其最终值。 如果 管道 指定标量(不包括值$null),而不是集合,则标量被视为一个元素的集合。 如果 管道 指定值 $null
,则 管道 被视为零元素的集合。
如果指定了 foreach-parameter-parallel
,则行为由实现定义。
仅在工作流中允许使用 foreach 参数‑parallel
(§8.10.2)。
每个 foreach 语句都有自己的枚举器 $foreach
(§2.3.2.2,§4.5.16),该枚举器仅在执行该循环时存在。
在 语句块 开始执行之前,将收集 管道 生成的对象。 但是,对于 ForEach-Object cmdlet,在生成每个对象时会对其执行 statement-block。
例子:
$a = 10, 53, 16, -43
foreach ($e in $a) {
...
}
$e # the int value -43
foreach ($e in -5..5) {
...
}
foreach ($t in [byte], [int], [long]) {
$t::MaxValue # get static property
}
foreach ($f in Get-ChildItem *.txt) {
...
}
$h1 = @{ FirstName = "James"; LastName = "Anderson"; IDNum = 123 }
foreach ($e in $h1.Keys) {
"Key is " + $e + ", Value is " + $h1[$e]
}
8.5 流控制语句
语法:
flow-control-statement:
break label-expression~opt~
continue label-expression~opt~
throw pipeline~opt~
return pipeline~opt~
exit pipeline~opt~
label-expression:
simple-name
unary-expression
描述:
流控制语句会导致无条件地将控制转移到其他位置。
8.5.1 break 语句
描述:
具有 label-expression 的 break 语句称为标记的 break 语句。 不带 label-expression 的 break 语句称为未标记的 break 语句。
在捕获语句之外,直接在迭代语句 (§8.4) 内的未标记的 break 语句将终止执行该最小封闭迭代语句。 直接在 switch 语句 (§8.6) 内的未标记的 break 语句将对当前 switch 的 switch-condition 终止模式匹配。 请参阅 (§8.8) 了解有关在捕获语句中使用中断的详细信息。
迭代语句或 switch 语句可以有选择性地紧靠在一个语句标签 (§8.1.1) 之前。此类语句标签可用作标记的 break 语句的目标,在这种情况下,该语句将终止执行目标封闭迭代语句。
标记的 break 不需要在任何本地范围内解析;即使跨越脚本和函数调用边界,匹配标签的搜索也可能继续沿着调用堆栈向上进行。 如果未找到匹配的标签,将终止当前命令调用。
由 标签表达式指定的标签的名称 不需要具有常量值。
如果
例子:
$i = 1
while ($true) { # infinite loop
if ($i * $i -gt 100) {
break # break out of current while loop
}
++$i
}
$lab = "go_here"
:go_here
for ($i = 1; ; ++$i) {
if ($i * $i -gt 50) {
break $lab # use a string value as target
}
}
:labelA
for ($i = 1; $i -le 2; $i++) {
:labelB
for ($j = 1; $j -le 2; $j++) {
:labelC
for ($k = 1; $k -le 3; $k++) {
if (...) { break labelA }
}
}
}
8.5.2 continue 语句
描述:
具有 label-expression 的 continue
语句称为标记的 continue 语句。 没有 label-expression 的 continue 语句称为 未标记的 continue 语句。
§8.8 中讨论了如何在捕获语句中使用 continue
。
循环中未标记的 continue
语句终止当前循环的执行,并将控制权传递到最小封闭迭代语句的结束大括号(§8.4)。 开关内未标记的 continue
语句将终止执行当前的 switch
迭代,并将控制权转交给最小的封闭 switch
的 switch-condition (§8.6)。
迭代语句或 switch
语句 (§8.6) 可以有选择性紧靠在一个语句标签 (§8.1.1) 之前。 此类语句标签可用作带标签的 continue
语句的目标,在这种情况下,该语句终止当前循环的执行或 switch
迭代,并将控制权传输到目标封闭迭代或 switch
语句标签。
标记的 continue
不需要在任何本地范围内解析;即使跨越脚本和函数调用边界,匹配标签的搜索也可以continue
沿着调用堆栈向上进行。 如果未找到匹配的标签,将终止当前命令调用。
由 标签表达式指定的标签的名称 不需要具有常量值。
如果
例子:
$i = 1
while (...) {
...
if (...) {
continue # start next iteration of current loop
}
...
}
$lab = "go_here"
:go_here
for (...; ...; ...) {
if (...) {
continue $lab # start next iteration of labeled loop
}
}
:labelA
for ($i = 1; $i -le 2; $i++) {
:labelB
for ($j = 1; $j -le 2; $j++) {
:labelC
for ($k = 1; $k -le 3; $k++) {
if (...) { continue labelB }
}
}
}
8.5.3 throw 语句
描述:
异常是一种处理系统级或应用程序级错误条件的方法。 throw 语句可引发异常。 (有关异常处理的讨论,请参阅 §8.7。
如果省略了 pipeline 并且 throw 语句不在 catch-clause 中,则行为由实现定义。 如果存在 pipeline 并且 throw 语句在 catch-clause 中,则在执行与 catch-clause 关联的任何 finally-clause 后,将重新引发由 catch-clause 捕获的异常。
如果存在 管道,则引发异常的类型由实现决定。
引发异常后,控件权将转交给可以处理异常的封闭 try 语句中的第一个 catch 子句。 最初引发异常的位置称为引发点。 引发异常后,将重复执行 §8.7 中所描述的步骤,直到找到与异常匹配的 catch 子句,或者找不到任何匹配的 catch 子句。
例子:
throw
throw 100
throw "No such record in file"
如果省略了 pipeline,并且 throw 语句不在 catch-clause 中,那么文本“ScriptHalted”会被写入管道,所引发的异常类型为 System.Management.Automation.RuntimeException
。
如果存在 pipeline,则引发的异常会包装在 System.Management.Automation.RuntimeException
类型的对象中,其中包含 System.Management.Automation.ErrorRecord
对象形式的异常的信息(可通过 $_
访问)。
示例 1:throw 123
导致了 RuntimeException类型的异常。 在 catch 块中,$_.TargetObject
包含封装在内部的对象,在本例中,此对象是一个值为 123 的 System.Int32
。
示例 2:throw "xxx"
导致了 RuntimeException类型的异常。 在 catch 块中,$_.TargetObject
包含封装在内部的对象,在本例中,此对象是一个值为“xxx”的 System.String
。
示例 3:throw 10,20
产生了一种 RuntimeException类型的异常。 在 catch 块中,$_.TargetObject
包含封装在内部的对象,在本例中,此对象是 System.Object[]
,它是不受约束的数组,由 System
.Int32` 值为 10 和 20 的两个元素组成。
8.5.4 return 语句
描述:
return
语句将 管道指定的值写入管道(如果有),并将控制权返回到函数或脚本的调用方。 函数或脚本可能具有零个或多个 return
语句。
如果执行到达函数的结束大括号,则假定为不带 pipeline 的隐含 return
。
return
语句是一种“语法糖”,允许程序员像使用其他语言一样表达自己;然而,函数或脚本返回的值实际上是该函数或脚本写入管道的所有值加上 pipeline 指定的任何值。 如果只将标量值写入管道,则其类型是返回的值的类型;否则,返回类型是一个不受约束的 1 维数组,其中包含写入管道的所有值。
例子:
function Get-Factorial ($v) {
if ($v -eq 1) {
return 1 # return is not optional
}
return $v * (Get-Factorial ($v - 1)) # return is optional
}
Get-Factorial
的调用方返回 int
。
function Test {
"text1" # "text1" is written to the pipeline
# ...
"text2" # "text2" is written to the pipeline
# ...
return 123 # 123 is written to the pipeline
}
Test
的调用方会返回一个包含三个元素的不受约束的一维数组。
8.5.5 exit 语句
描述:
exit 语句终止当前脚本,并将控件和 退出代码 返回到主机环境或调用脚本。 如果提供了 pipeline,则会在必要时将其指定的值转换为整数。 如果不存在此类转换,或者省略 管道,返回的将是 int 类型的零值。
例子:
exit $count # terminate the script with some accumulated count
8.6 switch 语句
语法:
switch-statement:
switch new-lines~opt~ switch-parameters~opt~ switch-condition switch-body
switch-parameters:
switch-parameter
switch-parameters switch-parameter
switch-parameter:
-regex
-wildcard
-exact
-casesensitive
-parallel
switch-condition:
( new-lines~opt~ pipeline new-lines~opt~ )
-file new-lines~opt~ switch-filename
switch-filename:
command-argument
primary-expression
switch-body:
new-lines~opt~ { new-lines~opt~ switch-clauses }
switch-clauses:
switch-clause
switch-clauses switch-clause
switch-clause:
switch-clause-condition statement-block statement-terimators~opt~
switch-clause-condition:
command-argument
primary-expression
描述:
如果 开关条件 指定单个值,则会将控件传递给一个或多个匹配的模式语句块。 如果没有模式匹配,则可以采取一些默认操作。
开关必须包含一个或多个 switch-clause,每个 switch-clause 都以模式(非默认 switch 子句)开头,或者以关键字 default
(默认的 switch 子句)开头。 开关必须包含零个或一个 default
switch 子句,以及零个或多个非默认 switch 子句。 Switch 语句可以按任意顺序编写。
多个模式的值可能相同。 模式不需要文本,并且开关可以具有不同类型的模式。
如果 开关条件的值 与模式值匹配,则执行该模式的 语句块。 如果多个模式值与 开关条件的值匹配,则每个匹配模式的 语句块 按词法顺序执行,除非其中任何 语句块包含 break
语句(§8.5.1)。
如果 开关条件 的值不匹配任何模式值,并且存在 default
开关子句,则将执行其 语句块;否则,该 开关条件 的模式匹配过程将终止。
开关可以嵌套,每个开关都有其自己的一套 switch 子句。 在这种情况下,switch 子句属于当前范围内最内层的 switch。
在进入每个 statement-block 时,$_
被自动赋予导致将控制权转交给该 statement-block 的 switch-condition 的值。 $_
也可用于该 statement-block 的 switch-clause-condition。
通过测试相等性来完成非字符串匹配(§7.8.1)。
如果匹配涉及字符串,则默认情况下,比较不区分大小写。 由于存在 switch-parameter-casesensitive
,因此比较区分大小写。
模式可能包含通配符(§3.15),在这种情况下,将执行通配符字符串比较,但前提是存在开关参数-wildcard。 默认情况下,比较不区分大小写。
模式可能包含正则表达式(§3.16),在这种情况下,将执行正则表达式字符串比较,但前提是存在 switch-parameter-regex
。 默认情况下,比较不区分大小写。 如果 -regex
存在并与模式匹配,则在 switch-clausestatement-block 中为该模式定义了 $matches
。
switch-parameter 可以缩写;可以使用参数的任何独特前导部分。 例如,‑regex
、‑rege
、‑reg
、‑re
和 ‑r
等效。
如果指定了冲突的 switch-parameter,则按词法以最后一个为准。 存在 ‑exact
会禁用 -regex
和 -wildcard
;然而,它不会影响 ‑case
。
如果指定了 switch-parameter‑parallel
,则行为由实现定义。
工作流中仅允许使用 switch-parameter‑parallel
(§8.10.2)。
如果模式是 script-block-expression,则系统会计算该块,并在必要时将结果转为布尔值。 如果结果的值为 $true
,则执行相应的 语句块;否则,不执行。
如果 switch-condition 指定了多个值,则会使用上面针对指定单个值的 switch-condition 描述的规则按词法顺序将开关应用于每个值。 每个 switch 语句都有自己的枚举器 $switch
(§2.3.2.2,§4.5.16),仅在执行该开关时才存在。
switch语句可能有一个标签,并且它可能包含带标签和未标记的break(§8.5.1)和continue(§8.5.2)语句。
如果 switch-condition 是 -file
switch-filename,则开关不会循环访问表达式中的值,而是循环访问 switch-filename 指定文件中的值。该文件每次读取一行,每行都包含一个值。 行终止符字符不包括在值中。
例子:
$s = "ABC def`nghi`tjkl`fmno @#$"
$charCount = 0; $pageCount = 0; $lineCount = 0; $otherCount = 0
for ($i = 0; $i -lt $s.Length; ++$i) {
++$charCount
switch ($s[$i]) {
"`n" { ++$lineCount }
"`f" { ++$pageCount }
"`t" { }
" " { }
default { ++$otherCount }
}
}
switch -wildcard ("abc") {
a* { "a*, $_" }
?B? { "?B? , $_" }
default { "default, $_" }
}
switch -regex -casesensitive ("abc") {
^a* { "a*" }
^A* { "A*" }
}
switch (0, 1, 19, 20, 21) {
{ $_ -lt 20 } { "-lt 20" }
{ $_ -band 1 } { "Odd" }
{ $_ -eq 19 } { "-eq 19" }
default { "default" }
}
8.7 try/finally 语句
语法:
try-statement:
try statement-block catch-clauses
try statement-block finally-clause
try statement-block catch-clauses finally-clause
catch-clauses:
catch-clause
catch-clauses catch-clause
catch-clause:
new-lines~opt~ catch catch-type-list~opt~
statement-block
catch-type-list:
new-lines~opt~ type-literal
catch-type-list new-lines~opt~ , new-lines~opt~
type-literalfinally-clause:
new-lines~opt~ finally statement-block
描述:
try 语句提供了一种机制,用于捕获在代码块执行期间发生的异常。 try 语句还提供指定在控件离开 try 语句时始终执行的代码块的功能。 §8.5.3中介绍了通过 throw 语句引发异常的过程。
try 块 是与 try 语句关联的 statement-block。 catch 块 是与 catch-clause 关联的 statement-block。 finally 块 是与 finally-clause 关联的 statement-block。
没有 catch-type-list 的 catch-clause 称为一般 catch 子句。
每个 catch-clause 都是异常处理程序,其 catch-type-list 包含引发异常的类型的 catch-clause 是匹配的 catch 子句。 一般 catch 子句将匹配所有异常类型。
虽然 catch-clauses 和 finally-clause 是可选的,但是必须至少存在其中一个子句。
处理引发的异常时将重复评估以下步骤,直到找到与异常匹配的 catch 子句。
在当前范围内,将检查每个包含抛出点的 try 语句。 对于每个 try 语句 S,将从最里层的 try 语句开始,依次评估以下步骤,直到达到最外层的 try 语句才结束:
如果 S 的
try
块包含引发点,并且 S 具有一个或多个 catch 子句,则会按词法顺序检查 catch 子句,以查找合适的异常处理程序。 指定异常类型或异常类型基类型的第一个 catch 子句被视为匹配项。 一般 catch 子句被视为任何异常类型的匹配项。 如果找到匹配的 catch 子句,则通过将控制转移到该 catch 子句块来完成异常处理。 在匹配的 catch 子句中,变量$_
包含当前异常的说明。否则,如果
try
块或 S 的catch
块包含引发点,并且 S 有一个finally
块,则控制权会转交给 finally 块。 如果finally
块引发另一个异常,则会终止当前异常的处理。 否则,当控制到达finally
块的末尾时,会继续处理当前异常。
如果异常处理程序未位于当前作用域中,则上述步骤将重复用于封闭范围,其引发点对应于从中调用当前作用域的语句。
如果异常处理最终终止所有作用域,表示该异常不存在处理程序,则行为未指定。
为了防止 try 块中出现不可访问的 catch 子句,catch 子句可能不会指定与同一 try 块中早先 catch 子句所指定类型相等或从其中派生的异常类型。
当控制权离开 try
语句时,会始终执行 finally
块的语句。 无论控制权转移是由于正常执行、执行 break
、continue
或 return
语句还是由于 try
语句出现异常所引起的,情况都是如此。
如果在执行 finally
块期间出现异常,则该异常会被传递到下一个封闭的 try
语句。 如果另一个异常正在处理过程中,该异常将丢失。 生成异常的过程在 throw
语句的说明中进一步讨论。
try
语句可以与 trap
语句共存;有关详细信息,请参阅 §8.8。
例子:
$a = new-object 'int[]' 10
$i = 20 # out-of-bounds subscript
while ($true) {
try {
$a[$i] = 10
"Assignment completed without error"
break
}
catch [IndexOutOfRangeException] {
"Handling out-of-bounds index, >$_<`n"
$i = 5
}
catch {
"Caught unexpected exception"
}
finally {
# ...
}
}
每个引发的异常都以 System.Management.Automation.RuntimeException
形式出现。 如果 try
块中存在特定于类型的 catch-clause,则会检查异常的 InnerException 属性,以尝试查找匹配项,例如上述 System.IndexOutOfRangeException
类型的匹配项。
8.8 陷阱语句
语法:
trap-statement:
*trap* new-lines~opt~ type-literal~opt~ new-lines~opt~ statement-block
描述:
带有和不带 type-literal 的 trap
语句分别类似于带有和不带 catch-type-list 的 catch
块 (§8.7),但 trap
语句一次只能捕获一种类型。
可以在同一 语句块中定义多个 trap
语句,其定义顺序无关。 如果在同一范围内定义了两个具有相同 type-literal 的 trap
语句,则按照词法,第一个语句用于处理匹配类型的异常。
与 catch
块不同,trap
语句与异常类型完全匹配;不会执行派生类型匹配。
发生异常时,如果当前范围中不存在匹配的 trap
语句,则会在封闭范围中搜索匹配的捕获语句,这可能涉及在调用脚本、函数或筛选器中查找,然后在调用方中查找等等。 如果查找最终终止了所有范围,并且表明不存在异常处理程序,那么行为将不确定。
trap
语句的 statement-body 仅在处理相应异常时执行,否则,执行将跳过它。
如果 trap
的 statement-body 正常退出,则默认情况下,会将错误对象写入错误流中,认为异常已处理,并且继续执行范围中紧跟在包含 trap
语句(导致异常可见)的语句后面的语句。 引发异常的原因可能存在于包含 trap
语句的命令所调用的命令中。
如果在 trap
的 statement-body 中执行的最后一个语句是继续的 (§8.5.2),则会禁止将错误对象写入错误流中,并且继续执行范围中紧跟在包含捕获语句(导致异常可见)的语句后面的语句。 如果在 trap
的 statement-body 中执行的最后一个语句中断 (§8.5.1),则会禁止将错误对象写入错误流,并且会重新引发异常。
在 trap
语句中,变量 $_
包含当前错误的说明。
请考虑以下情况:从 try
块内部引发的异常没有匹配的 catch
块,但在更高的块级别存在匹配的 trap
语句。 即使任何父范围具有匹配的 catch
块,在执行 try
块的 finally 子句之后,trap
语句仍然会获得控制权。 如果在 try
块本身中定义了 trap
语句,并且该 try
块具有匹配的 catch
块,则 trap
语句获取控制。
例子:
在以下示例中,编写了错误对象,并继续执行紧接在导致陷阱的语句后面的语句;也就是说,将“完成”写入管道。
$j = 0; $v = 10/$j; "Done"
trap { $j = 2 }
在以下示例中,禁止编写错误对象,并继续执行紧接在导致陷阱的语句后面的语句;也就是说,将“完成”写入管道。
$j = 0; $v = 10/$j; "Done"
trap { $j = 2; continue }
在以下示例中,禁止编写错误对象,并会重新引发异常。
$j = 0; $v = 10/$j; "Done"
trap { $j = 2; break }
在以下示例中,陷阱和异常生成语句位于同一范围内。 捕获并处理异常后,执行会继续,并将 1 写入管道。
&{trap{}; throw '\...'; 1}
在以下示例中,捕获和异常生成语句位于不同的范围内。 捕获并处理异常后,执行会继续,并将 2(而不是 1)写入管道。
trap{} &{throw '\...'; 1}; 2
8.9 data 语句
语法:
data-statement:
data new-lines~opt~ data-name data-commands-allowed~opt~ statement-block
data-name:
simple-name
data-commands-allowed:
new-lines~opt~ -supportedcommand data-commands-list
data-commands-list:
new-lines~opt~ data-command
data-commands-list , new-lines~opt~ data-command
data-command:
command-name-expr
描述:
数据语句创建 数据节,使该节的数据与代码分开。 此分隔支持单独的文本字符串资源文件等功能,例如错误消息和帮助字符串。 它还有助于支持国际化,以便更轻松地隔离、定位和处理将翻译成不同语言的字符串。
脚本或函数可以包含零个或多个数据节。
数据节 语句块 仅限于包含以下 PowerShell 功能:
- 除
-match
之外的所有运算符 if
语句- 以下自动变量:
$PsCulture
、$PsUICulture
、$true
、$false
和$null
。 - 评论
- 管道
- 用分号分隔的语句(
;
) - 文本
- 调用 ConvertFrom-StringData cmdlet
- 通过 supportedcommand 参数标识的任何其他 cmdlet
如果使用 ConvertFrom-StringData
cmdlet,则可以使用任何形式的字符串字面量来表示键/值对。 但是,"expandable-string-literal" 和 "expandable-here-string-literal" 不得包含任何变量替换或子表达式扩展。
例子:
SupportedCommand 参数指明给定的 cmdlet 或函数仅生成数据。
例如,以下数据部分包括用户编写的 cmdlet ConvertTo-XML
,该 cmdlet 格式化 XML 文件中的数据:
data -supportedCommand ConvertTo-XML {
Format-XML -strings string1, string2, string3
}
请考虑以下示例,其中数据节包含一个 ConvertFrom-StringData
命令,该命令将字符串转换为哈希表,其值分配给 $messages
。
$messages = data {
ConvertFrom-StringData -stringdata @'
Greeting = Hello
Yes = yes
No = no
'@
}
分别使用 $messages.Greeting
、$messages.Yes
和 $messages.No
访问哈希表的键和值。
现在,这可以另存为英语资源。 可以在单独的文件中创建德语和西班牙语资源,其中包含以下数据部分:
$messages = data {
ConvertFrom-StringData -stringdata @"
Greeting = Guten Tag
Yes = ja
No = nein
"@
}
$messagesS = data {
ConvertFrom-StringData -stringdata @"
Greeting = Buenos días
Yes = sí
No = no
"@
}
如果存在 dataname,它将命名用于存储数据语句值的变量(不使用前导 $
)。 具体而言,$name = data { ... }
等效于 data name { ... }
。
8.10 函数定义
语法:
function-statement:
function new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
filter new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
workflow new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
function-name:
command-argument
command-argument:
command-name-expr
function-parameter-declaration:
new-lines~opt~ ( parameter-list new-lines~opt~ )
parameter-list:
script-parameter
parameter-list new-lines~opt~ , script-parameter
script-parameter:
new-lines~opt~ attribute-list~opt~ new-lines~opt~ variable script-parameter-default~opt~
script-block:
param-block~opt~ statement-terminators~opt~ script-block-body~opt~
param-block:
new-lines~opt~ attribute-list~opt~ new-lines~opt~ param new-lines~opt~
( parameter-list~opt~ new-lines~opt~ )
parameter-list:
script-parameter
parameter-list new-lines~opt~ , script-parameter
script-parameter-default:
new-lines~opt~ = new-lines~opt~ expression
script-block-body:
named-block-list
statement-list
named-block-list:
named-block
named-block-list named-block
named-block:
block-name statement-block statement-terminators~opt~
block-name: one of
dynamicparam begin process end
描述:
函数定义 指定所定义的函数、筛选器或工作流的名称及其参数的名称(如果有)。 它还包含零个或多个语句,这些语句是为了实现该函数的目的而执行的。
每个函数都是类 System.Management.Automation.FunctionInfo
的实例。
8.10.1 筛选器函数
而普通函数在管道中运行一次,并通过 $input
访问输入集合,筛选器 是一种特殊的函数,它针对输入集合中的每个对象执行一次。
当前正在处理的对象可通过变量 $_
获得。
没有命名块(§8.10.7)的筛选器等效于具有进程块的函数,但没有任何开始块或结束块。
请考虑以下筛选器函数定义和调用:
filter Get-Square2 { # make the function a filter
$_ * $_ # access current object from the collection
}
-3..3 | Get-Square2 # collection has 7 elements
6, 10, -3 | Get-Square2 # collection has 3 elements
每个筛选器是类 System.Management.Automation.FilterInfo
的实例(§4.5.11)。
8.10.2 工作流函数
工作流函数类似于具有实现定义的语义的普通函数。 工作流函数将转换为一系列 Windows Workflow Foundation 活动并在 Windows Workflow Foundation 引擎中执行。
8.10.3 参数处理
请考虑对名为 Get-Power
的函数进行以下定义:
function Get-Power ([long]$base, [int]$exponent) {
$result = 1
for ($i = 1; $i -le $exponent; ++$i) {
$result *= $base
}
return $result
}
此函数具有两个参数,$base
和 $exponent
。 它还包含一组语句,对于非负指数值,这组语句将计算 $base^$exponent^
并将结果返回给 Get-Power
的调用方。
当脚本、函数或筛选器开始执行时,每个参数都会初始化为其相应的参数的值。 如果没有相应的参数,并且提供了默认值(§8.10.4),则使用该值;否则,将使用值 $null
。 因此,每个参数都是一个新变量,就像在 脚本块开始时通过赋值进行初始化一样。
如果 脚本参数 包含类型约束(如上述 [long]
和 [int]
),则相应的参数的值将根据需要转换为该类型;否则,不会发生转换。
当脚本、函数或筛选器开始执行时,变量 $args
将其定义为不受约束的 1 维数组,其中包含名称或位置不按词法顺序绑定的所有参数。
请考虑以下函数定义和调用:
function F ($a, $b, $c, $d) { ... }
F -b 3 -d 5 2 4 # $a is 2, $b is 3, $c is 4, $d is 5, $args Length 0
F -a 2 -d 3 4 5 # $a is 2, $b is 4, $c is 5, $d is 3, $args Length 0
F 2 3 4 5 -c 7 -a 1 # $a is 1, $b is 2, $c is 7, $d is 3, $args Length 2
有关参数绑定的详细信息,请参阅 §8.14。
8.10.4 参数初始化程序
参数 p 的声明可能包含一个初始化器,在这种情况下,如果 p 没有绑定到调用中的任何参数,那么该初始化器的值将用于初始化 p。
请考虑以下函数定义和调用:
function Find-Str ([string]$str, [int]$start_pos = 0) { ... }
Find-Str "abcabc" # 2nd argument omitted, 0 used for $start_pos
Find-Str "abcabc" 2 # 2nd argument present, so it is used for $start_pos
8.10.5 [switch] 类型约束
传递 switch 参数时,命令中的相应参数必须由类型开关约束。 类型开关有两个值,即 True 和 False。
请考虑以下函数定义和调用:
function Process ([switch]$trace, $p1, $p2) { ... }
Process 10 20 # $trace is False, $p1 is 10, $p2 is 20
Process 10 -trace 20 # $trace is True, $p1 is 10, $p2 is 20
Process 10 20 -trace # $trace is True, $p1 is 10, $p2 is 20
Process 10 20 -trace:$false # $trace is False, $p1 is 10, $p2 is 20
Process 10 20 -trace:$true # $trace is True, $p1 is 10, $p2 is 20
8.10.6 管道和函数
当管道中使用脚本、函数或筛选器时,值集合将传递到该脚本或函数。 脚本、函数或筛选器通过枚举器$input(§2.3.2.2,§4.5.16)访问该集合,该集合是在该脚本、函数或筛选器的条目上定义的。
请考虑以下函数定义和调用:
function Get-Square1 {
foreach ($i in $input) { # iterate over the collection
$i * $i
}
}
-3..3 | Get-Square1 # collection has 7 elements
6, 10, -3 | Get-Square1 # collection has 3 elements
8.10.7 命名块
脚本块 中的语句可以属于一个大型未命名块,也可以将其分发到一个或多个命名块中。 命名块允许自定义处理来自管道的集合;命名块可以按任意顺序定义。
在传递第一个管道对象之前,begin 块(即用关键字 begin 标记的块)中的语句会执行一次。
系统会对传递的每个管道对象执行 process 块(即使用关键字 process 标记的块)中的语句。 ($_
提供对来自管道的输入集合中正在处理的当前对象的访问。这意味着,如果通过管道发送零个元素的集合,则根本不执行进程块。 但是,如果在管道上下文之外调用脚本或函数,则此块恰好执行一次,并且 $_
设置为 $null
,因为没有当前集合对象。
在传递最后一个管道对象后,会执行一次 end 块(即用关键字 end 标记的块)中的语句。
8.10.8 dynamicparam 块
到目前为止,§8.10 的子节处理 静态参数,这些参数定义为源代码的一部分。 还可以通过 dynamicparam 块(另一种形式的命名块 (§8.10.7))定义 动态参数,该参数用关键字 dynamicparam
进行标记。 此机制的大部分内容是由实现定义的。
动态参数是仅在某些条件下可用的 cmdlet、函数、筛选器或脚本的参数。 Set-Item
cmdlet 的 Encoding 参数就是一个示例。
在 语句块中,使用 if 语句指定函数中可用参数的条件。 使用 New-Object cmdlet 创建实现定义类型的对象来表示参数,并指定其名称。 此外,使用 New-Object
创建不同实现定义类型的对象来表示参数的实现定义属性。
以下示例显示了一个函数,其标准参数名为 Name 和 Path,以及名为 DP1Int32
类型。 仅当 Path 参数的值包含“HKLM:”时,DP1 参数才在 Sample 函数中可用,指示它在 HKEY_LOCAL_MACHINE
注册表驱动器中使用。
function Sample {
Param ([String]$Name, [String]$Path)
dynamicparam {
if ($path -match "*HKLM*:") {
$dynParam1 = New-Object System.Management.Automation.RuntimeDefinedParameter("dp1", [Int32], $attributeCollection)
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = 'pset1'
$attributes.Mandatory = $false
$attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection``1[System.Attribute]
$attributeCollection.Add($attributes)
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("dp1", $dynParam1)
return $paramDictionary
}
}
}
用于创建对象以表示动态参数的类型是“System.Management.Automation.RuntimeDefinedParameter
”。
用于创建用于表示参数属性的对象的类型是System.Management.Automation.ParameterAttribute
。
参数的实现定义属性包括 Mandatory、Position 和 ValueFromPipeline。
8.10.9 param 块
参数块 提供了声明参数的替代方法。 例如,以下参数声明集等效:
function FindStr1 ([string]$str, [int]$start_pos = 0) { ... }
function FindStr2 {
param ([string]$str, [int]$start_pos = 0) ...
}
参数块 允许 参数块 上的 属性列表,而 函数参数声明 则不允许。
脚本可能有 参数块,但没有 函数参数声明。 函数或筛选器定义可能具有 函数参数声明 或 参数块,但不能同时具有两者。
请考虑以下示例:
param ( [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[string[]] $ComputerName )
$ComputerName
这个参数具有 string[]
类型,它是必需的,并从管道获取输入。
请参阅 §12.3.7,了解 参数 属性以及更多示例。
8.11 parallel 语句
语法:
parallel-statement:
*parallel* statement-block
并行语句包含以定义的实现方式执行的零个或多个语句。
仅在工作流中允许并行语句(§8.10.2)。
8.12 sequence 语句
语法:
sequence-statement:
*sequence* statement-block
序列语句包含以定义的实现方式执行的零个或多个语句。
序列语句仅在工作流中允许(§8.10.2)。
8.13 inlinescript 语句
语法:
inlinescript-statement:
inlinescript statement-block
inlinescript 语句包含零个或多个按照实现定义的方式执行的语句。
仅允许在工作流中使用内联脚本语句(§8.10.2)。
8.14 参数绑定
调用脚本、函数、筛选器或 cmdlet 时,每个参数都可以按位置绑定到相应的参数,第一个参数的位置为零。
请考虑对名为 Get-Power
的函数的以下定义片段及其调用:
function Get-Power ([long]$base, [int]$exponent) { ... }
Get-Power 5 3 # argument 5 is bound to parameter $base in position 0
# argument 3 is bound to parameter $exponent in position 1
# no conversion is needed, and the result is 5 to the power 3
Get-Power 4.7 3.2 # double argument 4.7 is rounded to int 5, double argument
# 3.2 is rounded to int 3, and result is 5 to the power 3
Get-Power 5 # $exponent has value $null, which is converted to int 0
Get-Power # both parameters have value $null, which is converted to int 0
调用脚本、函数、筛选器或 cmdlet 时,可以按名称将参数绑定到相应的参数。 这是通过将 参数与参数一起使用来完成的,该参数是具有前导短划线(-)的参数的名称,后跟该参数的关联值。 使用的参数名称可以具有任何不区分大小写的拼写,并且可以使用唯一指定相应参数的任何前缀。 选择参数名称时,请避免使用 常见参数的名称。
请考虑对函数 Get-Power
的以下调用:
Get-Power -base 5 -exponent 3 # -base designates $base, so 5 is
# bound to that, exponent designates
# $exponent, so 3 is bound to that
Get-Power -Exp 3 -BAs 5 # $base takes on 5 and $exponent takes on 3
Get-Power -e 3 -b 5 # $base takes on 5 and $exponent takes on 3
另一方面,调用以下函数
function Get-Hypot ([double]$side1, [double]$side2) {
return [Math]::Sqrt($side1 * $side1 + $side2 * $side2)
}
必须使用参数 -side1
和 -side2
,因为没有唯一指定参数的前缀。
不能多次使用同一参数名称,无论是否使用不同的关联参数值。
参数可以具有属性(§12)。 有关各个属性的信息,请参阅 §12.3中的各节。 有关参数集的信息,请参阅 §12.3.7。
脚本、函数、筛选器或 cmdlet 可以通过调用命令行、管道或两者接收参数。 下面是用于解析参数绑定的步骤:
- 绑定所有命名参数,然后
- 绑定位置参数,然后
- 通过完全匹配根据值 (§12.3.7) 从管道绑定,然后
- 通过转换根据值 (§12.3.7) 从管道绑定,然后
- 通过完全匹配根据名称 (§12.3.7) 从管道绑定,然后
- 通过转换根据名称 (§12.3.7) 从管道绑定
其中几个步骤涉及转换,如 §6 中所述。但是, 绑定中使用的转换集与语言转换中使用的转换集不同。 具体说来
- 尽管值
$null
可以强制转换为布尔值,但$null
不能绑定到bool
。 - 将值
$null
传递给 cmdlet 的 switch 参数时,将被视为传递$true
。 但是,当传递给函数的 switch 参数时,它被视为等同于传递$false
。 - bool 或 switch 类型的参数只能绑定到数值或布尔参数。
- 如果参数类型不是集合,但参数是某种集合,则除非参数类型为对象或 PsObject,否则不会尝试转换。 (此限制的要点是禁止将集合转换为字符串参数。否则,会尝试通常的转换。
如果参数类型 IList
或 ICollection<T>
,则仅尝试通过构造函数、op_Implicit和op_Explicit进行这些转换。 如果不存在此类转换,则使用“集合”类型的参数的特殊转换,其中包括 IList
、ICollection<T>
和数组。
如果可能,位置参数更倾向于在不使用类型转换的情况下进行绑定。 例如,
function Test {
[CmdletBinding(DefaultParameterSetname = "SetB")]
param([Parameter(Position = 0, ParameterSetname = "SetA")]
[decimal]$dec,
[Parameter(Position = 0, ParameterSetname = "SetB")]
[int]$in
)
$PsCmdlet.ParameterSetName
}
Test 42d # outputs "SetA"
Test 42 # outputs "SetB"