使用 ForEach-Object 而不是 foreach 时语法错误位置不精确

Sam*_*uel 4 error-handling powershell

大多数时候,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
}
Run Code Online (Sandbox Code Playgroud)

结果是

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
Run Code Online (Sandbox Code Playgroud)

该线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
}
Run Code Online (Sandbox Code Playgroud)

现在的错误更加有用,它实际上指向错误9Write-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
Run Code Online (Sandbox Code Playgroud)

这是否与管道运营商的管道性质有关,或者我理解有误。当中间有更多函数时,第一种情况使得错误跟踪变得非常困难。在大多数情况下,我可以切换到foreach,但我很想了解实际发生的情况。

mcl*_*ton 5

这可能过于简单化了,具有更深入正式知识的人可能有更精确的技术答案,但值得注意的是,forforeach是内置的 PowerShell 语句(在文档链接中称为“语言命令”),其中包含嵌套的声明列表:

\n
for (<Init>; <Condition>; <Repeat>)\n{\n    <Statement list>\n}\n\nforeach ($<item> in $<collection>)\n{\n    <statement list>\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它们是 PowerShell语法的一部分,引擎知道正在执行列表中的哪个语句,而ForEach-Object“只是”一个 cmdlet,例如Invoke-CommandGet-ChldItem

\n
ForEach-Object\n    ... snip ...\n    [-Begin    <ScriptBlock>]\n    [-Process] <ScriptBlock[]>\n    [-End      <ScriptBlock>]\n    ... snip ...\n
Run Code Online (Sandbox Code Playgroud)\n

因此,您的foreach-object示例相当于:

\n
1 | $myScriptBlock = {\n2 |     $entry = $_\n3 | \n4 |     Test-Something $entry\n5 | }\n6 |\n7 | @("a", "b") | foreach-object -process $myScriptBlock\n
Run Code Online (Sandbox Code Playgroud)\n

ForEach-Object\ 的位置参数允许您使用类似于for/ 的语法调用 cmdlet foreach,但底层机制不同,就 PowerShell 引擎而言,报告错误的顶级代码行是第 7 行,而不是4.

\n

它不会追溯到定义脚本块的源代码行 - 在您的情况下它可能可以,但我猜测在脚本块是从动态代码生成的更人为的情况下会很困难,例如

\n
1 | $myScriptBlock = [scriptblock]::Create(\n2 |     "`$entry = `$_" +\n3 |     "`r`n" + "`r`n" + "`r`n" +\n4 |     "Test-Something `$entry"\n5 | )\n6 |\n7 | @("a", "b") | foreach-object -process $myScriptBlock\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,应该报告哪个行号的错误?

\n

更新

\n

根据 @zett42\'s 的评论重新堆栈跟踪,PowerShell确实包含有关哪一行在异常属性中引发异常的信息$_.ScriptStackTrace- 例如参见:

\n
Set-StrictMode -Version Latest\n$ErrorActionPreference = "Stop"\n\n[cultureinfo]::CurrentUICulture = \'en-US\'\n\nfunction Test-Something($val)\n{\n    # var is not defined here\n    Write-Host($undefined_variable)\n}\n\ntry\n{\n    $myScriptBlock = [scriptblock]::Create(\n        "`$entry = `$_" + "`r`n" + "`r`n" +\n        "Test-" +\n        "Something `$entry"\n    )\n    @("a", "b") | foreach-object -process $myScriptBlock\n}\ncatch\n{\n    $_.ScriptStackTrace\n    throw\n}\n
Run Code Online (Sandbox Code Playgroud)\n

运行这个给出:

\n
C:\\> pwsh C:\\src\\so\\stack.ps1\nat Test-Something, C:\\src\\so\\stack.ps1: line 9\nat <ScriptBlock>, <No file>: line 3\nat <ScriptBlock>, C:\\src\\so\\stack.ps1: line 19\nForEach-Object: C:\\src\\so\\stack.ps1:19\nLine |\n  19 |      @("a", "b") | foreach-object -process $myScriptBlock\n     |                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n     | The variable \'$undefined_variable\' cannot be retrieved because it has not been set.\n
Run Code Online (Sandbox Code Playgroud)\n

所以简单的答案是,PowerShell 看起来只是报告异常冒泡到的顶级行。for在and的情况下foreach,这是包含嵌套语句的行,对于foreach-objectit 来说,这是包含嵌套语句的行foreach-object它来说,就是出现的

\n

并回答我自己的反问:

\n
\n

错误应该报告哪一行?

\n
\n

对于动态脚本块,它\xe2\x80\x99是相对于脚本块源的内部行号(而不是主脚本的行号) - 请参阅:

\n
at <ScriptBlock>, <No file>: line 3\n
Run Code Online (Sandbox Code Playgroud)\n

  • 我认为您正在寻找的术语是“流程控制语句”(代替“语言命令”):) (2认同)