PowerShell - テキストファイルの文字コードを変換する方法 - Qiita のコード、便利そうだなーと思ったんですが、一方でファイルの取得やら、フォルダ構造を維持した出力やらを他のコマンドレットにやらせたいなーと思ったりもしました。具体的には次のように書きたいと思ったわけです。
-
ls ./conv_enc -r | ConvertTo-WinContent
-
./conv_enc
配下の UTF-8(改行コード:LF)のファイルを SJIS(改行コード:CR LF) のファイルに一括で変換
-
-
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も参考に、実装。
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
という自動変数を使うとラッパー関数が短く書けることを知りました。
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
ラッパー関数を書くためのメタい方法があるらしく、次のようにも書けるっぽいです(正しいコードなのか謎)。
$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