エンコードされた文字のコードを調べたい、あるいは文字コードから文字を調べたいということはよくある。そういう場合、専用ツールを使う手もあるが、Windows PowerShellでも文字と文字コードの変換は可能だ。
ただ、PowerShellは文字の扱いについて微妙なのと、ファイルのエンコード、デコードは可能だが、文字列や文字を対象にしたエンコード、デコードのコマンドを持っていないため、ちょっと分かりづらい。
インターネット検索で「PowerShell 文字コード」や「PowerShell 文字コード変換」を調べても、ファイルの文字コード変換の方法や文字化け解消の方法しか引っかからない。
そこで、PowerShellでの文字と文字コードの扱いについて解説ページを作ることにした。
文字コードを扱う
文字コードから文字へ
PowerShellには、文字リテラルを表記する方法がないので、文字コードを表す整数を[char]
にキャストする。 内部コードは、UTF-16LE
(LE:Little Endian)で、これをPowerShellではUnicode
と呼ぶ。
※以下の実行例では、行頭が“> ”のものは入力コマンド、それ以外はコマンドの出力を表す
> [char]0x41
A
> [char]0x4e9c
亜
> [char[]]@(0x41,0x42,0x43)
A
B
C
文字列に文字コードで表現された文字を埋め込むには、
> "aaaa$([char]0x4e9c)bbbb"
aaaa亜bbbb
とする。これはサロゲート文字にも対応できるが2文字になる
> $y="$([char]0xd840)$([char]0xdc0b)"
> $y
𠀋
> [int[]][char[]]$y | %{ $_.tostring("X")}
D840
DC0B
一部の特殊文字に関しては、逆クオート記法で表現できる。
表記 | コード | 意味 |
---|---|---|
`0 | 0x00 | NUL |
`a | 0x07 | BEL |
`b | 0x08 | BS |
`f | 0x0c | FF |
`n | 0x0d 0x0a | CR LF |
`r | 0x0d | CR |
`t | 0x09 | HT |
`v | 0x0b | VT |
PowerShell 7.x以降では、
`u{ユニコード}
でユニコード文字を
`e
でエスケープ(0x1b/ESC)を表現できる
文字から文字コードへ
PowerShell内部では16bit Unicode(UTF-16LE)で文字を扱っている。文字は、[char]'c'
で扱えるので、これを[int]
でキャストすれば、内部文字コードに変換できる。[int]
として扱うので、エンディアンの影響を受けない。
> [int][char]'a'
97
> [int][char]'う'
12358
>([int][char]'う').tostring("X")
3046
> ([int][char]'a').tostring("X")
61
ただし、Unicodeの場合、一部の漢字がサロゲートペアになり内部的には2文字扱いとなる。なので、キャストする場合には、[int[]][char[]]
としなければならない。
これはint
の配列として返ってくる。このため、1文字としての対処が難しい。例えば、16進数に.ToString(書式)
で変換する場合にForEach-Objectを使わねばならない。“%”は、ForEach-Objectのエイリアスである。
> $x
𠀋
> [int[]][char[]]$x | %{$_.tostring("X")}
D840
DC0B
> [int][char]$x
値 "𠀋" を型 "System.Char" に変換できません。エラー: "String には一文字しか使用できません。"
発生場所 行:1 文字:1
+ [int][char]$x
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) []、RuntimeException
+ FullyQualifiedErrorId : InvalidCastParseTargetInvocation
文字に関するその他のこと
string.toCharArray()
文字列は「string.toCharArray()」でcharの配列に分割可能である。分割すれば、すべての文字列は文字の配列として扱える。それぞれの文字を文字コード(内部コードなのでUTF-16)に変換できる。
> ('a日本語'.ToCharArray()) | %{([int][char]$_).ToString("x")}
61
65e5
672c
8a9e
エンコードを変換したい
たとえば、UTF-8に変換したいなら.NET FrameworkのSystem.Text.Encodingクラスを使って変換を行う。具体的には、[System.Text.Encoding]::UTF8.GetBytes(<string>)
メソッドを使う。引数の<string>
がUTF-16(Unicode)文字列であることに注意。UTF-32にも変換できる。
また、サロゲート文字にも対応可能。なお[クラス]::メソッドは、PowerShellで.NET Frameworkのクラスにあるメソッド/プロパティを実行するための記法。このやり方で、.NET Frameworkのクラスが利用できる。
System.Text.Encodingクラスには、デコーダーも含まれるのでUTF-8/32を含め他の文字コードからUnicodeへの変換も可能。
【準備】サロゲート文字を$yに定義
#$yはサロゲート文字
> $y="$([char]0xd840)$([char]0xdc0b)"
> [int[]][char[]]$y | %{ $_.tostring("X4")}
D840
DC0B
> $y
𠀋
UTF-16LEに変換
バイト配列で扱うとエンディアンが見えるようになる。
> [System.Text.Encoding]::Unicode.GetBytes($y) | %{ $_.tostring("X2")}
40
D8
0B
DC
UTF-32に変換
[System.Text.Encoding]::UTF32.GetBytes($y) | %{ $_.tostring("X2")}
0B
00
02
00
UTF-8に変換
> [System.Text.Encoding]::UTF8.GetBytes($y) | %{ $_.tostring("X2")}
F0
A0
80
8B
GetBytesは文字列も受け付け可能
GetBytes
は[byte[]]になるので文字列を与えることも可能
[System.Text.Encoding]::UTF8.GetBytes('日本語') | %{ $_.tostring("X2")}
E6
97
A5
E6
9C
AC
E8
AA
9E
毎回、コマンドを打つのも面倒なので、エンコーダーを変数に定義しておくと便利。
> $U8Encoder=[System.Text.Encoding]::UTF8
> $U32Encoder=[System.Text.Encoding]::UTF32
> $U16Encoder=[System.Text.Encoding]::Unicode
> $U8Encoder.GetBytes($y)
240
160
128
139
利用頻度が高いならプロファイル($PROFILE)で定義しておくこともできる。
Unicode(UTF-16LE)以外でエンコードされた文字を表示させたい
[System.Text.Encoding]::<エンコード>.GetString(byte[])
を使えば、他のエンコード方式のbyte配列を文字列(=Unicode)として出力できる。
> $y
𠀋
> [System.Text.Encoding]::UTF8.GetBytes($y)
240
160
128
139
> [System.Text.Encoding]::UTF8.GetString([System.Text.Encoding]::UTF8.GetBytes($y))
𠀋
これも同じく、変数に定義したエンコーダーを使うと短く表示できる
> $utf8bytes=$U8Encoder.GetBytes($y)
> $utf8bytes
240
160
128
139
> $U8Encoder.GetString($utf8bytes)
𠀋
文字をUnicode(内部コード)で16進ダンプしたい
文字列を.ToCharArray()
で分解したあとに[int[]][char[]]
をキャストして書式指定文字"X"を使って表示書式を変更する。内部コードなのでUnicodeとなる。
> [int[]][char[]]('う日本語'.ToCharArray()) | %{$_.ToString("X")}
3046
65E5
672C
8A9E
文字をUTF-8で16進ダンプしたい
> $U8Encoder.GetBytes("aう日本語$($y)") | %{ if($_ -ge 0xC2){ Write-Host "" } Write-Host ('0x{0:X2} ' -f $_) -NoNewline }
0x61
0xE3 0x81 0x86
0xE6 0x97 0xA5
0xE6 0x9C 0xAC
0xE8 0xAA 0x9E
0xF0 0xA0 0x80 0x8B
文字を“U+XXXX”形式で文字コードを表示したい
UTF-32のbyte列に変換して、BitCoverterで4つごとにint32に変換、これを16進数で表示する。
> 'U+'+[System.BitConverter]::ToInt32($U32Encoder.GetBytes($y),0).toString("X")
U+2000B
[System.BitConverter]
は静的クラスであり、オブジェクト(インスタンス)を作ることができないため[System.Text.Encoding]
のように変数には格納できない。毎回打つか、関数を作る。
function global:U_Code($x) {
return ('U+{0:X4}' -f [System.BitConverter]::ToInt32($U32Encoder.GetBytes($x),0))
}
実行例
> U_Code('a')
U+61
> U_Code('う')
U+3046
> U_Code($y)
U+2000B
> U_Code("$([char]0xd840)$([char]0xdc0b)")
U+2000B
Unicodeのコードポイント表記は、旧Unicodeの定義では、「U+<4桁の16進数>」となっているため、書式を"X4”とした。ただし、のちに32bitに拡張されたとき、「U-<8桁の16進数>」が追加で定義されている。しかし、ここでは、接頭辞を「U+」として、最低限4桁の16進数を出力するものとした。
参考
The Unicode Standard, Version 3.0,1991 Preface,Page xxix
0 件のコメント:
コメントを投稿