Powershell功能处理或中止处理程序

Mar*_*cki 3 powershell cmdlets

我有一个管道函数,它在begin块中分配一些需要在最后处理的资源.我已尝试在end块中执行此操作但在函数执行中止时不会调用它,例如ctrl+ c.

如何修改以下代码以确保$sw始终处置:

function Out-UnixFile([string] $Path, [switch] $Append) {
    <#
    .SYNOPSIS
    Sends output to a file encoded with UTF-8 without BOM with Unix line endings.
    #>
    begin {
        $encoding = new-object System.Text.UTF8Encoding($false)
        $sw = new-object System.IO.StreamWriter($Path, $Append, $encoding)
        $sw.NewLine = "`n"
    }
    process { $sw.WriteLine($_) }
    # FIXME not called on Ctrl+C
    end { $sw.Close() }
}
Run Code Online (Sandbox Code Playgroud)

编辑:简化功能

Jer*_*ert 5

不幸的是,没有好的解决方案.确定性清理似乎是PowerShell中的明显遗漏.它可以像引入一个cleanup总是被调用的新块一样简单,无论管道如何结束,但是,即使版本5似乎也没有提供任何新东西(它引入了类,但没有清理机制).

也就是说,有一些不那么好的解决方案.最简单的,如果你枚举$input变量而不是使用begin/ process/ end你可以使用try/ finally:

function Out-UnixFile([string] $Path, [switch] $Append) {
    <#
    .SYNOPSIS
    Sends output to a file encoded with UTF-8 without BOM with Unix line endings.
    #>
    $encoding = new-object System.Text.UTF8Encoding($false)
    $sw = $null
    try {
        $sw = new-object System.IO.StreamWriter($Path, $Append, $encoding)
        $sw.NewLine = "`n"
        foreach ($line in $input) {
            $sw.WriteLine($line)
        }
    } finally {
        if ($sw) { $sw.Close() }
    }
}
Run Code Online (Sandbox Code Playgroud)

这有一个很大的缺点,即你的函数将保持整个管道,直到一切都可用(基本上整个函数被视为一个大块end),如果你的函数是为了处理大量输入,这显然是一个交易破坏者.

第二种方法是坚持使用begin/ process/ end和手动过程控制-C作为输入,因为这是真正的问题的位.但绝不是唯一有问题的位,因为在这种情况下你也想要处理异常 - end基本上没有用于清理的目的,因为只有在成功处理整个管道时才会调用它.这需要一个邪恶的组合trap,try/ finally和标志:

function Out-UnixFile([string] $Path, [switch] $Append) {
  <#
  .SYNOPSIS
  Sends output to a file encoded with UTF-8 without BOM with Unix line endings.
  #>
  begin {
    $old_treatcontrolcasinput = [console]::TreatControlCAsInput
    [console]::TreatControlCAsInput = $true
    $encoding = new-object System.Text.UTF8Encoding($false)
    $sw = new-object System.IO.StreamWriter($Path, $Append, $encoding)
    $sw.NewLine = "`n"
    $end = {
      [console]::TreatControlCAsInput = $old_treatcontrolcasinput
      $sw.Close()
    }
  }
  process {
    trap {
      &$end
      break
    }
    try {
      if ($break) { break }
      $sw.WriteLine($_)
    } finally {
      if ([console]::KeyAvailable) {
        $key = [console]::ReadKey($true)
        if (
          $key.Modifiers -band [consolemodifiers]"control" -and 
          $key.key -eq "c"
        ) { 
          $break = $true
        }
      }
    }
  }
  end {
    &$end
  }
}
Run Code Online (Sandbox Code Playgroud)

虽然详细,但这是我能提出的最简单的"正确"解决方案.它确实通过扭曲来确保Control-C状态正确恢复,并且我们从不尝试捕获异常(因为PowerShell在重新抛出它们时很糟糕); 如果我们不关心这些细节,解决方案可能会稍微简单一些.我甚至不会尝试就性能发表声明.:-)

如果有人有关于如何改进这一点的想法,我会全力以赴.显然,检查Control-C可以考虑到函数,但除此之外,似乎很难使它更简单(或至少更具可读性),因为我们被迫使用begin/ process/ end模具.