コマンドプロンプトなコマンドの入出力が文字化けする

前提

  • 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


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS

Last-modified: 2020-05-09 (土) 09:14:15