大多数时候,powershell 中的错误报告非常有用。我看到了错误,看到了起源,但我注意到 ForEach-Object 管道中的任何错误都会丢失其起源行,并且错误堆栈仅指向带有ForEach-Object
.
错误位置示例
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
[cultureinfo]::CurrentUICulture = 'en-US'
function Test-Something($val)
{
# var is not defined here
Write-Host($undefined_variable)
}
@("a","b") | ForEach-Object{
$entry = $_
Test-Something $entry
}
结果是
ForEach-Object : The variable '$undefined_variable' cannot be retrieved because it has not been set.
In D:\dummy.ps1:12 Line:14
+ @("a","b") | ForEach-Object{
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (undefined_variable:String) [ForEach-Object], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand
该线12
指向@("a","b") | ForEach-Object{
,这显然不是错误位置。
foreach 的更有用的示例
现在我使用的是foreach
(只有最后4行代码发生了变化)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
[cultureinfo]::CurrentUICulture = 'en-US'
function Test-Something($val)
{
# var is not defined here
Write-Host($undefined_variable)
}
$data = @("a","b")
foreach($entry in $data)
{
Test-Something $entry
}
现在的错误更加有用,它实际上指向错误9
行Write-Host($undefined_variable)
The variable '$undefined_variable' cannot be retrieved because it has not been set.
In D:\dummy.ps1:9 Line:16
+ Write-Host($undefined_variable)
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (undefined_variable:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : VariableIsUndefined
这是否与管道运营商的管道性质有关,或者我理解有误。当中间有更多函数时,第一种情况使得错误跟踪变得非常困难。在大多数情况下,我可以切换到foreach
,但我很想了解实际发生的情况。
这可能过于简单化了,具有更深入正式知识的人可能有更精确的技术答案,但值得注意的是,
for
和foreach
是内置的 PowerShell 语句(在文档链接中称为“语言命令”),其中包含嵌套语句列表:它们是 PowerShell语法的一部分,引擎知道正在执行列表中的哪个语句,而
ForEach-Object
“只是”一个 cmdlet,例如Invoke-Command
或Get-ChldItem
。因此,您的
foreach-object
示例相当于:ForEach-Object
的位置参数允许您使用类似于for
/ 的语法调用 cmdletforeach
,但底层机制不同,就 PowerShell 引擎而言,报告错误的顶级代码行是第 7 行,而不是第 4 行。它不会追溯到定义脚本块的源代码行 - 在您的情况下它可能可以,但我猜测在脚本块是从动态代码生成的更人为的情况下会很困难,例如
在这种情况下,应该报告哪个行号的错误?
更新
根据 @zett42 的评论重新堆栈跟踪,PowerShell确实包含有关哪一行在异常属性中引发异常的信息
$_.ScriptStackTrace
- 例如参见:运行这个给出:
所以简单的答案是,PowerShell 看起来只是报告异常冒泡到的顶级行。
for
在and的情况下foreach
,这是包含嵌套语句的行,而 for it 是出现的foreach-object
行。foreach-object
并回答我自己的反问:
对于动态脚本块,它是相对于脚本块源的内部行号(而不是主脚本的行号) - 请参阅:
要获得更快、信息更丰富的 ForEach-Object 版本,请使用
.{process {
<code>}}
。该版本更加优化,执行时间更短,但可读性较差,并且缺少 ForEach-Object 的一些功能。然而,由于很少使用额外的功能,因此在大多数情况下它是一对一的替代品。如果您希望使用
-Begin
or-End
,请根据需要取消注释begin {}
orend {}
行并添加所需的代码。这段代码:
产生以下输出:
解释:
(跳至第 7 条,了解 2 至 6 的汇总)
begin {}
函数或脚本块中的process {}
、 和end {}
部分是为处理管道输入而设计的。该begin {}
部分在处理任何管道输入之前执行,process {}
对管道上的每个项目执行一次,然后end {}
执行该部分。begin {}
是,您可以在其中定义函数以及可用于累积与管道上的项目相关的信息(例如计数或总和)的变量。end {}
是,您可以构建所需的管道输出并在脚本块退出之前发送它(这可能包括计数或总和等信息)。process {}
部分为每个管道项目执行一次,每个项目分配给自动变量$_
,并且不使用 cmdlet 来获得额外的性能提升工作完成了。表现:
Santiago 在评论中指出 Call 运算符
&
是内部优化的,而 Dot Sourcing 运算符则.
没有。我相信他通过查看 PowerShell 的源代码找到了这一点。考虑到这一点,使用 Call 运算符应该比 Dot Sourcing 运算符具有更好的性能,因此运行了性能测试。在计算机上运行测试的问题是外部进程可能会导致性能问题,从而干扰 PowerShell 代码的执行时间。为了将这些问题均匀地分配到每个方法上,Call 运算符、Dot Sourcing、Foreach 循环和 Foreach-Object cmdlet 的测试结果涉及轮流执行的每个方法,以求和一百万个随机数 (0-99),总共每个方法 100 次执行。总时间计算为每个方法的所有执行的总和。
如下表所示,呼叫操作员
&
用最短的时间执行了任务。PowerShell 5.1.19041.3803
PowerShell 7.3.9
呼叫操作员
&
测试代码:点采购运算符
.
测试代码:Foreach循环测试代码:
Foreach-Object cmdlet 测试代码:
运行测试的代码:
打印测试结果: