🤯

PowerShell の日本語処理がよくわからない

2020/12/01に公開

2023-11 追記:

https://github.com/PowerShell/PowerShell/issues/20786

一向にそのままなので思い切って Issue を立ててみたところ、PowerShell の問題というよりは PowerShell 7.1 以降に採用された .NET の仕様によるものだと教えてもらえました。
PowerShell の外側の話になるので $Profile でどうにかできるものではなく、どうしても変更したいなら Windows の環境変数をいじることになるそうです。

さすがに環境変数は他のアプリへの影響などが大きそうで怖いし、かといって毎回 [System.StringComparison]::OrdinalIgnoreCase と入力するのは大変…… ということで、下記の内容を $PROFILE に書いて独自のメソッドを作成して使っています。

Update-TypeData -TypeName "System.String" -Force -MemberType ScriptMethod -MemberName CaseSensitiveEquals -Value {
    param([string]$s)
    return [string]::Equals($this, $s, [System.StringComparison]::Ordinal)
}

Update-TypeData -TypeName "System.String" -Force -MemberType ScriptMethod -MemberName CaseInSensitiveEquals -Value {
    param([string]$s)
    return [string]::Equals($this, $s, [System.StringComparison]::OrdinalIgnoreCase)
}

(追記ここまで)


環境:

> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      7.1.0
PSEdition                      Core
GitCommitId                    7.1.0
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

何が起きたか

> echo あ い う え お ぁ ぃ ぅ ぇ ぉ|sort -Unique
あ
い
う
え
お

!?

> "あ" -eq "ぁ"
True

!!??

何が起きているのか

現時点の PowerShell (バージョン7.1.0)の文字列比較をそのまま使うと ひらがなの大小は区別されないようです

(ちなみにカタカナは区別されている様子↓)

> echo ア イ ウ エ オ ァ ィ ゥ ェ ォ|Sort -Unique
ア
ァ
イ
ィ
ウ
ゥ
エ
ェ
オ
ォ

> "ア" -eq "ァ"
False

しかし、 GetEnumerator() で文字列から配列を生成するとひらがなの大小は区別される模様。

> "あいうえおぁぃぅぇぉ".GetEnumerator()|sort -Unique
ぁ
あ
ぃ
い
ぅ
う
ぇ
え
ぉ
お

もしやと思い型を調べてみると、案の定というか文字列型( [string] )で起こる現象のようです。

> echo あ い う|%{$_.GetType().Name}
String
String
String

> "あいう".GetEnumerator()|%{$_.GetType().Name}
Char
Char
Char

> [char]"あ" -eq [char]"ぁ"
False # 期待通り!

ここまでわかってからしばらくというもの、その都度 [Char] に変換する書き方をしていました(この書き方でも最終的な出力結果の型を見ると [string] になります。これはこれで不可思議…)。

> echo あ い う え お ぁ ぃ ぅ ぇ ぉ|sort {$_ -as [char]} -Unique
ぁ
あ
ぃ
い
ぅ
う
ぇ
え
ぉ
お

何が起きていたのか

ある日、手癖で下のように入力したところ……

> echo あ い う え お ぁ ぃ ぅ ぇ ぉ|sort -Unique -CaseSensitive
ぁ
あ
ぃ
い
ぅ
う
ぇ
え
ぉ
お

区 別 さ れ て い る

まさかと思い試してみると、下記の結果は False 。つまり、当初の謎挙動は大小ひらがなを アルファベットの大文字小文字と同じように扱っている 結果だったようです。

> "あ" -ceq "ぁ"
False

この原則は配列要素の検索にも適用されるようです。

> (echo あ い う え お) -contains "ぁ"
True # これが予測できずにハマること数日…

> (echo あ い う え お) -ccontains "ぁ"
False

> "あいうえお".GetEnumerator() -contains "ぁ"
False # これは char 型を返すので期待通り

> "ゃ" -in (echo や ゆ よ)
True # -in 演算子も同様

「英語に関しては大文字小文字区別しなくてもいいや」と -eq-in 演算子で比較作業をしていたところ、拗音が区別できていなかったために地獄を見ました。

回避策

文字列型の Equals() メソッドでは、オプションで大文字小文字の違いを無視するようにしても大小ひらがなは別の文字として扱ってくれるようです。

> "あいう".Equals("あぃう")
False

> "あいうAbc".Equals("あいうabc")
False

> "あいうAbc".Equals("あいうabc", [StringComparison]::OrdinalIgnoreCase)
True

> "あいうAbc".Equals("あぃうabc", [StringComparison]::OrdinalIgnoreCase)
False # ひらがな部分が一致しないと正しく別物として判定してくれている

> (echo あ い う え お).where({$_.Equals("ぁ", [System.StringComparison]::OrdinalIgnoreCase)})
# 配列の Contains() には大文字小文字の無視が指定できないようなので Where() メソッド

補足:さらなる混沌へ

どうしてローマ数字がアルファベットとして判定されるんですか?

> (echo Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ)  -contains "VI"
True

> "Ⅳ" -eq "IV"
True

> "Ⅳ" -ceq "IV"
False

> "Ⅹ" -eq "X"
True

ちなみに Windows PowerShell では起きない現象です。なんという罠…。

Discussion