【问题标题】:Cannot call exit() from a method within a custom object无法从自定义对象中的方法调用 exit()
【发布时间】:2016-09-26 11:46:45
【问题描述】:

我正在尝试使用this 答案中描述的 PS 模块来模拟类。但是,从这样的“实例方法”中exit 似乎是不可能的:

$sb =
{
    function bar
    {
        "bar"
    }

    function quux
    {
        exit
    }

    Export-ModuleMember -Function bar,quux
}
$foo = New-Module $sb -AsCustomObject
$foo.bar()
$foo.quux()

结果

bar
Exception calling "quux" with "0" argument(s): "System error."
At line:17 char:1
+ $foo.quux()
+ ~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ScriptMethodFlowControlException
  1. 首先为什么会发生这种情况,为什么异常如此神秘?
  2. 正确执行此操作的规范方法是什么?我确实想退出并停止所有进一步的执行,而不是恢复控制流。我还需要能够返回退出代码。

【问题讨论】:

  • 嗨,PS v5 不支持类吗?
  • @sodawillow 确实如此,但它必须是可移植的,所以我不能依赖 v5 功能。

标签: powershell powershell-module


【解决方案1】:

您可以抛出异常并使用trap 捕获它并退出脚本:

trap{
    exit
}

$sb =
{
    function bar
    {
        "bar"
    }

    function quux
    {
        throw "Stopping execution"
    }

    Export-ModuleMember -Function bar,quux
}
$foo = New-Module $sb -AsCustomObject
$foo.bar()
$foo.quux()

【讨论】:

  • 不,我想立即停止所有执行。
  • 这显然会引发一个异常,它不允许我“优雅地”退出(而且它会产生输出 - 异常 - 这是一个禁忌),它也不允许我退出具体退出代码。它并没有真正回答为什么我的最小示例首先会引发异常。感谢您尝试提供帮助,但这并不能解决我的问题:(
  • 我没有仔细阅读您的问题。不过,我可能会为您找到解决方案。
  • 我不知道为什么,但这只是在为我调用 $foo.quux() 后恢复了控制流。
  • 你可能需要设置$ErrorActionPreference = 'stop'
【解决方案2】:

首先为什么会发生这种情况,为什么异常如此神秘?

您看到的行为似乎是一个错误(在 PowerShell 7.0 中仍然存在;请参阅this GitHub issue):

从附加到[pscustomobject] 实例的脚本方法调用exit - 脚本方法是否通过@987654322 间接附加@ 或直接通过
Add-Member -MemberType ScriptMethod - 意外失败,出现您看到的神秘错误:错误不是退出,而是作为 语句 终止错误发出(请参阅下面)并默认执行继续

PowerShell 5+ class 上定义的方法受到影响

正确执行此操作的规范方法是什么?我确实想退出并停止所有进一步的执行,而不是恢复控制流。我还需要能够返回退出代码。

通常,exit 是从 脚本 文件的顶级范围调用的,而不是从函数内部(无论是否在模块内部)。

请注意,exit 要么立即退出封闭脚本,要么,如果在交互式会话中直接调用该函数,存在整个会话

如果您的 quux() 方法是从脚本中调用的,并且您确实想立即退出封闭脚本,则在 PowerShell 4- 中唯一的直接解决方法是尝试模拟 PowerShell 类并直接定义和调用quux 函数

但是,间接解决方法是从Martin Brandl's helpful answerthrow 方法添加一个小调整:


解决方案因 PowerShell 对终止错误的不一致使用而变得复杂,分为两类:

  • 语句-终止错误,(默认情况下)仅终止正在执行的语句,然后继续执行。

  • 脚本-终止错误(致命错误),它会终止整个执行(脚本及其所有调用者,在调用 PowerShell 的 CLI 的情况下, PowerShell 进程作为一个整体)。

this GitHub docs issue 中描述了这种令人惊讶的区别和 PowerShell 令人惊讶的复杂错误处理。

附加到[pscustomobject] 实例的脚本方法(如在您的动态模块方法中使用的那样)只能生成语句终止错误,不是脚本终止的。

同样,PowerShell 5+ class 方法 的行为不同:它们创建 脚本-终止错误

要实现整体终止同时还报告退出代码[1],您还需要在中执行以下操作调用者的范围:

  • 捕获/捕获语句终止错误。
  • 发出exit $n 语句作为响应(在脚本范围的上下文中会成功),其中$n 是所需的退出代码。

你有两个选择:

  • trap 语句添加到调用者的作用域,该语句会触发语句和脚本终止错误并调用exit $n,其中$n 是所需的退出代码。

    • 为此,对 Martin 的答案的唯一调整是将 trap { exit } 替换为
      trap { exit 1 } 以报告退出代码 1

    • throwing 函数传达特定的退出代码需要额外的工作 - 见下文。

    • 请注意,trap 很少使用,但它可以为您提供一个简单的解决方案。

  • 或者,在方法调用或可能引发错误的一组语句周围使用try / catch 语句,并在catch 处理程序中调用exit
    try { $foo.quux() } catch { exit 1 }


设置特定退出代码的解决方法:

# Create a custom object with methods,
# via a dynamic module.
$foo = New-Module -AsCustomObject {
    function bar
    {
        "bar"
    }
    function quux
    {
      # Throw a statement-terminating error with the desired exit code,
      # to be handled by the `trap` statement in the caller's scope.      
      throw 5
    }
}

# Set up the trap statement for any terminating error.
# Note: Normally, you'd place the `trap` statement at the
#       beginning of the script.
trap {
  # Get the exit code from the most recent error or default to 1.
  # Note: $_ is a *wrapper* error record arund the original error record
  #       created with `throw`; however, the wrapper's .ToString() representation
  #       is the (string representation of) the original object thrown.
  if (-not ($exitCode = $_.ToString() -as [int])) { $exitCode = 1 }
  exit $exitCode
}

$foo.bar()
$foo.quux() # This triggers the trap.

# Getting here means that the script will exit with exit code 0
# (assuming no other terminating error occurs in the remaining code).

[1] 如果脚本自动由于 脚本 终止错误而终止,因为基于class 的解决方案会生成它的退出代码总是报告为1(并打印错误消息),但只有如果脚本是通过PowerShell的CLI调用powershell -file ...powershell -commmand ...) ;相比之下,在 interactive PowerShell 会话中 $LASTEXITCODE 在这种情况下没有设置(但会打印错误消息)。
因此,即使 PS 5+ 解决方案是一个选项,您可能仍希望捕获脚本终止错误并将其显式转换为具有特定退出代码的 exit 调用,如下所示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-26
    • 1970-01-01
    • 2014-10-12
    • 2015-06-17
    • 1970-01-01
    • 2012-06-24
    • 2012-07-31
    相关资源
    最近更新 更多