PowerShell で文字コードと改行コードの変換


[PowerShell - テキストファイルの文字コードを変換する方法 - Qiita](http://qiita.com/kurukurupapa@github/items/74518fad687332c3c09b) のコード、便利そうだなーと思ったんですが、一方でファイルの取得やら、フォルダ構造を維持した出力やらを他のコマンドレットにやらせたいなーと思ったりもしました。具体的には次のように書きたいと思ったわけです。

1. `ls ./conv_enc -r | ConvertTo-WinContent`
    * `./conv_enc` 配下の UTF-8(改行コード：LF)のファイルを SJIS(改行コード：CR LF) のファイルに一括で変換

2. `cp ./win_enc/* -dest ./unix_enc -r -pass | ConvertTo-UnixContent`
   * `./win_enc` 配下の SJIS(改行コード：CR LF)のファイルを `./unix_enc` にフォルダ構造を維持しつつコピーして UTF-8(改行コード：LF)のファイルに一括で変換

## 実装

[PowerShell Windows形式⇔UNIX形式でのテキスト出力(改行コード調整) - YOMON8.NET](http://yomon.hatenablog.com/entry/2014/05/29/PowerShell_Windows%E5%BD%A2%E5%BC%8F%E2%87%94UNIX%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%81%AE%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E5%87%BA%E5%8A%9B%28%E6%94%B9%E8%A1%8C%E3%82%B3%E3%83%BC%E3%83%89%E8%AA%BF)も参考に、実装。

```powershell
filter Convert-Encoding
{
  Param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String] $FullName,

    [ValidateSet("String","UTF8")] # 使いたい Encoding を適宜増やす
    [String] $FromEncoding,

    [ValidateSet("String","UTF8")] # 使いたい Encoding を適宜増やす
    [String] $ToEncoding,

    [String] $NewLineChar,
    [Switch] $PassThru
  )

  if (Test-Path $FullName -PathType Container) {return}

  $content = Get-Content $FullName -Encoding $FromEncoding

  if ($null -ne $content -and $content.length -gt 0)
  {
    $content -join $NewLineChar | Set-Content $FullName -Encoding $ToEncoding
    # V3 なら -Raw で読み込んで改行コードを置換したほうが良いかも
  }

  if ($PassThru) {$_}
}

filter ConvertTo-WinContent
{
  Param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String] $FullName,

    [ValidateSet("String","UTF8")] # 使いたい Encoding を適宜増やす
    [String] $Encoding = "UTF8",

    [Switch] $PassThru
  )

  $param = @{
    FullName     = $FullName
    FromEncoding = $Encoding
    ToEncoding   = "String"
    NewLineChar  = "`r`n"
    PassThru     = $PassThru
  }

  Convert-Encoding @param
}

filter ConvertTo-UnixContent
{
  Param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String] $FullName,

    [ValidateSet("String","UTF8")] # 使いたい Encoding を適宜増やす
    [String] $Encoding = "String",

    [Switch] $PassThru
  )

  $param = @{
    FullName     = $FullName
    FromEncoding = $Encoding
    ToEncoding   = "UTF8"
    NewLineChar  = "`n"
    PassThru     = $PassThru
  }

  Convert-Encoding @param
}
```

## 感想

~~PowerShellだと、とりあえず汎用的／抽象的な関数を作って特殊化／具体化するとき辛い気がします。`Set-Alias`に`ArgumentList`みたいな引数で部分適用できたら楽なのではとたまに思います。~~

あと、`DirectoryInfo`、`FileInfo`オブジェクトにおいて、フルパスが格納されてるプロパティーは`FullName`ですが、`ValueFromPipelineByPropertyName`を考えたら、`FullName`のエイリアスプロパティとして、`Path`を設定しておいてくれたら楽なのではとたまに思います。V3以降なら `% fullname`、V2以前なら `%{$_.fullname}`を噛ませばいいだけですが、やはり`ValueFromPipelineByPropertyName`を違和感なく使えたほうがオブジェクト指向とパイプラインがエレガントに融合できている感じがして良いと思うのです。

## 追記：2015/09/23

### $PSBoundParameters

という自動変数を使うとラッパー関数が短く書けることを知りました。

*  [スプラッティングを使用して Windows PowerShell スクリプトを簡略化する方法について教えてください](https://gallery.technet.microsoft.com/scriptcenter/b9dffd0c-3732-47a2-8052-29313b12aee1)

```powershell
filter ConvertTo-WinContent
{
  Param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String] $FullName,

    [ValidateSet("String","UTF8")] # 使いたい Encoding を適宜増やす
    [String] $FromEncoding = "UTF8",

    [Switch] $PassThru
  )

  Convert-Encoding -ToEncoding String -NewLineChar "`r`n" @PSBoundParameters
}

filter ConvertTo-UnixContent
{
  Param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String] $FullName,

    [ValidateSet("String","UTF8")] # 使いたい Encoding を適宜増やす
    [String] $FromEncoding = "String",

    [Switch] $PassThru
  )

  Convert-Encoding -ToEncoding UTF8 -NewLineChar "`n" @PSBoundParameters
}
```


### Proxy Function

ラッパー関数を書くためのメタい方法があるらしく、次のようにも書けるっぽいです（正しいコードなのか謎）。

* [Proxy Functions: Spice Up Your PowerShell Core Cmdlets](http://blogs.technet.com/b/heyscriptingguy/archive/2011/03/01/proxy-functions-spice-up-your-powershell-core-cmdlets.aspx)

```powershell
$tMeta  = [System.Management.Automation.CommandMetaData]
$tProxy = [System.Management.Automation.ProxyCommand]

$newFunction = {
  $Metadata = New-Object $tMeta (Get-Command Convert-Encoding)
  [void] $MetaData.Parameters.Remove("ToEncoding")
  [void] $MetaData.Parameters.Remove("NewLineChar")
  $sb = $tProxy::Create($Metadata) -replace $default -replace $cmd
  Invoke-Expression "function ${name} {${sb}}"
  $sb
}

$name    = 'ConvertTo-WinContent'
$default = @('{FromEncoding}', '{FromEncoding} = "UTF8"')
$cmd     = @(
  'wrappedCmd @PSBoundParameters',
  'wrappedCmd -ToEncoding String -NewLineChar "`r`n" @PSBoundParameters'
)
. $newFunction

$name       = 'ConvertTo-UnixContent'
$default[1] = '{FromEncoding} = "String"'
$cmd[1]     = 'wrappedCmd -ToEncoding UTF8 -NewLineChar "`n" @PSBoundParameters'
. $newFunction
```