Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
260 views
in Technique[技术] by (71.8m points)

cmdlets - Powershell function dispose or abort handler

I have a pipe function that allocates some resources in begin block that need to be disposed at the end. I've tried doing it in the end block but it's not called when function execution is aborted for example by ctrl+c.

How would I modify following code to ensure that $sw is always disposed:

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() }
}

EDIT: simplified function

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Unfortunately, there is no good solution for this. Deterministic cleanup seems to be a glaring omission in PowerShell. It could be as simple as introducing a new cleanup block that is always called regardless of how the pipeline ends, but alas, even version 5 seems to offer nothing new here (it introduces classes, but without cleanup mechanics).

That said, there are some not-so-good solutions. Simplest, if you enumerate over the $input variable rather than use begin/process/end you can use 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() }
    }
}

This has the big drawback that your function will hold up the entire pipeline until everything is available (basically the whole function is treated as a big end block), which is obviously a deal breaker if your function is intended to process lots of input.

The second approach is to stick with begin/process/end and manually process Control-C as input, since this is really the problematic bit. But by no means the only problematic bit, because you also want to handle exceptions in this case -- end is basically useless for purposes of cleanup, since it is only invoked if the entire pipeline is successfully processed. This requires an unholy mix of trap, try/finally and flags:

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
  }
}

Verbose as it is, this is the shortest "correct" solution I can come up with. It does go through contortions to ensure the Control-C status is restored properly and we never attempt to catch an exception (because PowerShell is bad at rethrowing them); the solution could be slightly simpler if we didn't care about such niceties. I'm not even going to try to make a statement about performance. :-)

If someone has ideas on how to improve this, I'm all ears. Obviously checking for Control-C could be factored out to a function, but beyond that it seems hard to make it simpler (or at least more readable) because we're forced to use the begin/process/end mold.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...