このブログの更新は X(旧Twitter)アカウント @m_hiyama で通知されます。
Follow @m_hiyama

ご連絡は上記 X アカウントに DM にてお願いします。

参照用 記事

PowerShellの困った話:文字エンコーディング

PowerShellでは、echo、cat、lsといったよく知られたコマンドは最初から用意されています。ただし、これらは別名(alias)であって、PowerShell本来の名前は Write-Output、Get-Content、Get-ChildItem です。コマンドラインから対話的に使うには短い名前が便利なので別名を使うことにします。

さて、コマンドラインの例:

PS C:\tmp> echo hello
hello
PS C:\tmp> echo hello > hello.txt
PS C:\tmp> cat hello.txt
hello
PS C:\tmp> 

特に珍しいことは何もないですよね。しかし、

PS C:\tmp> ls hello.txt


    ディレクトリ: C:\tmp


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2014/10/25     13:04         16 hello.txt


PS C:\tmp>

あれ? ファイルのサイズが変ですよ。bashに移って、

$ od -x hello.txt
0000000 feff 0068 0065 006c 006c 006f 000d 000a
0000020

$

左端は行番号みたいなもの(バイトオフセット)なので、データは feff, 0068, 0065, 006c, 006c, 006f, 000d, 000a、最初がバイトオーダーマーク、最後は改行の CR, LF ですが、16ビット単位です。そうです、UTF-16なんですよね。

echo(Write-Output)に文字エンコーディングを指定するようなパラメータ(オプション)はありません。リダイレクト記号「>」を使う代わりに次のようにすると文字エンコーディングを指定できます。

PS C:\tmp> "hello" | out-file -encoding:utf8 hello.txt
PS C:\tmp> 

それでファイルサイズは?

PS C:\tmp> ls hello.txt


    ディレクトリ: C:\tmp


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2014/10/25     13:28         10 hello.txt


PS C:\tmp>

んんー? 3バイト余計です。

$ od -x hello.txt
0000000 bbef 68bf 6c65 6f6c 0a0d
0000012

$ od -o hello.txt

ああー、またBOM(バイトオーダーマーク)だ。BOMなしのUTF-8の指定はないのか?

PS C:\tmp> "hello" | out-file -encoding:utf8n hello.txt
Out-File : パラメーター 'Encoding の引数を確認できません。引数 "utf8n" は、ValidateSet 属
性で指定されたセット "unknown、string、unicode、bigendianunicode、utf8、utf7、utf32、asci
i、default、oem" に属していません。このセットの引数を指定して、コマンドを再度実行してくだ
さい。
発生場所 行:1 文字:30
+ "hello" | out-file -encoding:utf8n hello.txt
+                              ~~~~~
    + CategoryInfo          : InvalidData: (:) [Out-File]、ParameterBindingValidationExce
    ption
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Comm
   ands.OutFileCommand

PS C:\tmp>

ダメみたい。調べたけど、BOMなしUTF-8の出力はできないようです。自前でどうにかするしかないようなので、次の関数を書きました。

function Get-Fullpath ([string] $path) {
    $current = (pwd).path
    $path = [IO.Path]::Combine($current, $path)
    [IO.Path]::GetFullPath($path)
}

function Out-FileInUtf8N ([string] $path){
    begin {
	$text = ""
    }
    process {
	$text += ($_  + "`r`n")
    }
    end {
	$Utf8N = New-Object System.Text.UTF8Encoding($false)
	[System.IO.File]::WriteAllText((Get-Fullpath $path), $text, $Utf8N)
    }
}

Set-Alias out8 Out-FileInUtf8N

なんとかなったかな。

PS C:\tmp> "hello" | out8 hello.txt
PS C:\tmp> ls hello.txt


    ディレクトリ: C:\tmp


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2014/10/25     14:14          7 hello.txt


PS C:\tmp> "こんにちは" | out8 hello-ja.txt
PS C:\tmp> ls hello-ja.txt


    ディレクトリ: C:\tmp


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2014/10/25     14:15         17 hello-ja.txt


PS C:\tmp>

ちょっと面倒くさいなー。

あーそれと、PowerShellのecho(Write-Output)は、cmd.exeのechoとは挙動が違いますから要注意。

PS C:\tmp> echo hello world
hello
world
PS C:\tmp> echo hello> hello.txt
hello>
hello.txt
PS C:\tmp>