【问题标题】:Powershell Finally block skipped with Ctrl-CPowershell finally 使用 Ctrl-C 跳过块
【发布时间】:2017-12-19 15:30:04
【问题描述】:

我正在使用 Try/Finally 在 Powershell 中编写一个监控脚本,以便在脚本结束时记录一条消息。该脚本旨在无限期运行,因此我想要一种跟踪意外退出的方法。

所有其他 StackOverflow 帖子和 Help page 我都检查过状态:

即使您使用 CTRL+C 停止脚本,Finally 块也会运行。如果 Exit 关键字从 Catch 块中停止脚本,也会运行 finally 块。

在实践中,我还没有发现这是真的。我正在使用以下人为示例来测试这一点:

Try {
    While($True) {
        echo "looping"
        Start-Sleep 3
    }
} Finally {
    echo "goodbye!"
    pause
}

这里的 Finally 块在每次 Ctrl+C 之后都会被跳过(无回显,无暂停),无论是作为保存的脚本运行还是执行时通过内置的 Powershell ISE。我得到的唯一输出是:

looping
looping
...(repeat until Ctrl-C)

我显然遗漏了一些东西,但我不知道它是什么,尤其是在这么小的代码 sn-p 中。

【问题讨论】:

  • 来自同一页面:“请注意,按 CTRL+C 会停止管道。发送到管道的对象不会显示为输出。因此,如果包含要显示的语句,如“Finally 块已运行”,按 CTRL+C 后不会显示,即使 finally 块已运行。”尝试将echo 替换为$finallyRan = true,或其他不生成管道输出的语句。
  • 很奇怪,但如果我将echo 换成Write-Host,你的脚本就可以正常工作。检查那里是否真的需要回声。
  • 顺便说一句,确定性清理 inside 管道是 really unnecessarily difficult 正是因为这种管道停止能力,仅仅依靠 finally 是行不通的。
  • @Vesper Echo 是 Write-Output 的别名,它喷射到输出 (&1) 流。 Write-Host 不写入任何流
  • @b_c 不是真的..."goodbye!" | Out-file logfile.txt 工作得很好。

标签: powershell powershell-3.0 try-finally


【解决方案1】:

正确的答案是 Ctrl+C 会破坏管道,正如该链接中所述,echo 使用管道来处理其输出。因此,一旦您 Ctrl+C,写入管道会导致脚本块出错,并且不会处理任何进一步的命令。因此,不要使用直接将输出发送到 stdout 的命令,其中有很多是间接使用管道的。另一方面,Write-Host 不使用管道,因此不会引发错误。

【讨论】:

    【解决方案2】:

    功能代码

    这会给你我相信你所追求的行为:

    Try {
        While($True) {
            echo "looping"
            Start-Sleep 3
        }
    } Finally {
        Write-Host "goodbye!"
        pause
    }
    

    参考文献

    Write-Output/echo - Synopsis

    将指定的对象发送到管道中的下一个命令。 如果命令是管道中的最后一个命令,则对象将显示在控制台中。

    Write-Host - Synopsis

    将自定义输出写入主机。

    Try-Catch-Finally - Syntax note

    请注意,按 CTRL+C 会停止管道。 发送到管道的对象不会显示为输出。因此,如果您包含要显示的语句,例如“Finally block has run”,它将按下 CTRL+C 后不会显示,即使 finally 块运行。

    说明

    根据 TheIncorrigible1 的评论和 Vesper 的回答,关键是管道已停止。但这不是因为Write-Output 中的错误。而且我认为它本身并不是一个令人满意的解释。

    • “如果命令是管道中的最后一个命令,则对象将显示在控制台中。” - 在 finally 块中,此语句似乎是错误的。但是,显式传递给 Out-Host 将产生所需的输出。
    • 在 Try-Catch-Final 注意
      • 引用部分令人困惑,因为它适用于发送到管道的未处理对象
      • 发送到管道并在Finally 块中处理的对象都可以。
      • 它谈到 “即使 finally 块已运行”,但如果前面有 Write-Outputpause 不会运行。

    更多代码

    Finally 块中运行了一些东西来调查行为,并与 cmets 一起了解会发生什么。

    } Finally {
        Write-Output "goodbye!" | Out-Default # works fine
        pause
    }
    

    } Finally {
        Write-Output "goodbye!" | Out-Host    # works fine
        pause
    }
    

    } Finally {
        pause                     # works fine
        Write-output "goodbye!"   # not executed
    }
    

    } Finally {
        try{
            Write-Output "goodbye!" -ErrorAction Stop
        }catch{
            Write-Host "error caught"   # this is not executed.
        }                               # $error[0] after script execution is empty
        pause
    }
    

    } Finally {
        try{
            ThisCommandDoesNotExist
        }catch{
            Write-Host "error caught"   # this is executed
        }                               # $error[0] contains CommandNotFoundException      
        pause
    }
    

    【讨论】:

    • 我假设原始脚本块输出仍然传递给Out-Default,你有一个管道,并且它被破坏的原因。但是,为什么pause 不起作用?
    • @Vesper pause 在自己的作品上。我假设因为 Write-Output 部分没有(仍然试图准确地弄清楚 why),所以未执行 finally 块的其余部分。你的意思是Write-Output 将内容作为对象传递给Out-Default编辑:(再次)你能解释一下Out-Default关于Write-Output吗?
    • 是的,这以某种方式确实破坏了代码执行,也就是说,echo "goodbye" 抛出了一些(不太确定是哪个)错误,导致 finally 块的其余部分被跳过。将代码包装到另一个 try-catch 中不起作用,但包装到 try-finally 中确实使“finally”块起作用。 Powershell 处理 Ctrl+C 序列有些奇怪。
    • @Vesper 阅读the last paragraph。管道已停止。
    猜你喜欢
    • 2012-07-11
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    • 2017-09-11
    • 2013-06-25
    • 2017-07-18
    • 1970-01-01
    • 2018-11-20
    相关资源
    最近更新 更多