コマンドプロンプトなコマンドの入出力が文字化けする
前提
- PowerShellやWindowsの内部エンコーディングはUTF16
- 日本語WindowsだとコンソールのエンコーディングはSJISが既定になっている
- SJISとUTF16の変換は自動で行われ、PowerShellなコマンドだけを使うなら通常は問題ない
- しかし、コマンドプロンプトなコマンドは入出力が通常はSJISが想定されており、
- Linux由来のコマンドは入出力は通常はUTF8(またはUS-ASCII)が想定されている
- そのためコマンドレットと非PowerShellなコマンドを混ぜて使うとエンコーディングが違って文字化けすることがある
- これを解決するには、(1)非PowerShellなコマンドのエンコーディングを変更するか、(2)PowerShellのエンコーディングを変更するか、どちらか
- 非PowerShellなコマンドはエンコーディングが固定になっていることが多いので、ここでは(2)のPowerShellのエンコーディングを変更して問題を解決する
- なお、エンコーディングは入力と出力それぞれ考慮する必要がある
PowerShellの出力エンコーディングをSJISへ変更する
PS> Get-Date (1)
2020年5月6日 17:42:00
PS> Get-Date | clip.exe (2)
- (1)は化けずにそのままコンソールに出力される
- (2)はclip.exeがUTF16に対応してないので、クリップボードには文字化けした文字列が入っている
- (ちなみにclip.exeは文字列をクリップボードにペーストすると昔からWindowsにあるコマンド)
- これを解決するには以下のようにする
PS> $OutputEncoding = [Text.Encoding]::Default (1)
PS> Get-Date | clip.exe
- (1)のように$outputEncodingをSJISに指定するとPowerShellの出力がSJISになるので、clip.exeが文字列を読み込めるようになる
補足
PS> [Text.Encoding]::GetEncoding('sjis')
BodyName : iso-2022-jp
EncodingName : 日本語 (シフト JIS)
HeaderName : iso-2022-jp
WebName : shift_jis
WindowsCodePage : 932
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.InternalEncoderBestFitFallback
DecoderFallback : System.Text.InternalDecoderBestFitFallback
IsReadOnly : True
CodePage : 932
PowerShellの入力エンコーディングを変更する
PS> $OutputEncoding = [Text.Encoding]::GetEncoding('utf-8') (1)
PS> gc .\1.json -Encoding utf8 | jq.exe (2)
{ (3)
"a": "日本語",
"b": "英語"
}
- (1)でPowerShellの出力をUTF8にしたので、(3)で|からUTF8が出力されて、jq.exeはJSONをパースできている
- なお、1.jsonファイルがBOMなしのUTF8の場合はgc(Get-Content)がエンコーディングを自動判定できないので、-Encoding utf8でエンコーディングを明示する
- ここまではOK
PS> gc .\1.json | jq.exe | Select-String "日本語" (1)
(2)
PS> [Console]::OutputEncoding = [Text.Encoding]::UTF8 (3)
PS> gc .\1.json | jq.exe | Select-String "日本語" (4)
"a": "日本語", (5)
- しかし、(1)のようにjq.exeの出力をPowerShellのSelect-Stringに渡すと、(2)のようにマッチに失敗する
- (jq.exeがBOMなしのUTF8を出力していて、|がそれを認識できないから?)
- これを解決するには(3)のようにPowerShellの入力をUTF8にしてから、(4)のようにSelect-Stringに渡すと、(5)のようにマッチに成功する
補足:なんでこんな問題が起きるのか
- Linuxのコマンドはパイプでつなげて使うが、パイプからは文字列が来る想定で設計されている(文字列はバイト列扱いで何も処理されない)
- 一方で、PowerShellのコマンドはパイプからオブジェクトが来る想定で設計されている(文字列は文字列オブジェクトに変換処理される)
- したがって、PowerShellをPowerShellらしく使いたいなら、オブジェクトを処理できないコマンド(=Linux由来のコマンド)は使わない方がいい
- 今回の場合なら下のようにWSLのBashを呼び出すとかした方が面倒がない
Bash呼び出し
PS> bash.exe -c 'cat 1.json | jq "." | grep "日本語"'
"a": "日本語",
CMD呼び出し
PS> cmd /c 'type 1.json | jq "." | jvgrep "日本語"'
"a": "日本語",
fzfを使うならこんな感じ、
PS> bash -c "find . | fzf"
参考
https://ladybug.hatenadiary.org/entry/20111203/p1