cmdからWSLを使って日本語文字列をパイプで扱うとcmdのコードページが壊れる
いまいちなタイトルですが、正確には「コマンドプロンプトからWSLを使ってマルチバイト文字*1をパイプで扱うとコマンドプロンプトのコードページが壊れる」という現象に遭遇しています。恐らくコマンドプロンプト(cmd.exe)ではなくconhost.exeまたはWSLのバグなのではないかと思うのですが…。
前提環境
日本語版Windows 10 Proに、WSL版Ubuntu18.04を普通にMicrosoftストアからインストールした環境です。
C:\temp>ver Microsoft Windows [Version 10.0.17763.253] C:\temp>chcp 現在のコード ページ: 932 C:\temp>wsl uname -srvmpio Linux 4.4.0-17763-Microsoft #253-Microsoft Mon Dec 31 17:49:00 PST 2018 x86_64 x86_64 x86_64 GNU/Linux C:\temp>wsl echo $LANG C.UTF-8
発生事象
冒頭に書いた通り「コマンドプロンプトからWSLを使ってマルチバイト文字をパイプで扱うとコマンドプロンプトのコードページが壊れる」状態となります。
ここで言う「壊れる」とは、MS932と見せかけて実はUTF8で動作しているような状態を指しています。
自分で書いておきながら何を言っているか判りにくいですが、以下の一連のコマンドで再現できます。
予め、"テストデータ"という文字列がShift-JIS及びUTF-8で記録されたsjis.txt及びutf8.txtを用意したうえで、各コマンドを実行すると以下のような現象が発生します。
C:\temp>chcp 現在のコード ページ: 932
日本語版WindowsのコマンドプロンプトのデフォルトのコードページはMS932で、chcpコマンドでも932であることが確認できます。
C:\temp>type sjis.txt テストデータ
MS932ですので、Shift-JISのテキストファイルは正常に表示されます。
C:\temp>type utf8.txt 繝・せ繝医ョ繝シ繧ソ
MS932ですので、UTF-8のテキストファイルは文字化けして表示されますが、正常な挙動です。ここまでは特に問題はありません。
C:\temp>wsl cat utf8.txt | wsl iconv -f utf8 -t sjis > conv2sjis.txt
WSLでUTF-8のファイルを表示し、パイプで引き渡し、WSLでiconvでUTF-8からShift-JISに文字コード変換し、別ファイルにリダイレクトしています。以降の挙動がおかしいのです。
C:\temp>chcp Active code page: 932
コードページを確認すると932と表示されるものの、何故か「現在のコード ページ: 932」ではなく英語で「Active code page: 932」と表示されています。
C:\temp>type sjis.txt �e�X�g�f�[�^
当初から何も手を加えていないShift-JISのテキストファイルが文字化けして表示されます。MS932なら正常表示されるはずです。
C:\temp>type utf8.txt テストデータ
当初から何も手を加えていないUTF-8のテキストファイルが文字化けせずに表示されます。MS932なら文字化けして表示されるはずです。このことからコードページが932(MS932)ではなく65001(UTF-8)に変わってしまっていると考えられます。
C:\temp>type conv2sjis.txt �e�X�g�f�[�^
WSLを使ってマルチバイト文字をパイプで流した後にWSLのiconvでShift-JISに変換してリダイレクトしたファイルも文字化けして表示されます。
C:\temp>chcp 932 現在のコード ページ: 932
ここで明示的にコードページを932に切り替えます。
C:\temp>type sjis.txt テストデータ
Shift-JISのテキストファイルが正常に表示される状態に戻っています。
C:\temp>type utf8.txt 繝・せ繝医ョ繝シ繧ソ
UTF-8のテキストファイルが正常に(意図通りに)文字化けして表示される状態に戻っています。
C:\temp>type conv2sjis.txt テストデータ
WSLを使ってマルチバイト文字をパイプで流した後にWSLのiconvでShift-JISに変換してリダイレクトしたファイルも正常に表示されます。
補足
以下のように、パイプを使わなければコードページは壊れませんでした
wsl iconv utf8.txt -f utf8 -t sjis > conv2sjis.txt
この例のように単にファイルの文字コード変換だけなら、iconvのオプションに入力ファイルを指定すればよいだけで、再現手順内のコマンドのようにわざわざパイプを使う意味はありません。
再現手順では概念説明のために無駄にパイプを使用しましたが、UTF-8文字列をsedやawkで加工した結果をiconvでShift-JISに変換したいためパイプを使いたい場合も同様の不具合が発生するのです。
回避策
WSLとcmd.exe(またはconhost.exe)が標準入出力のやり取りをする過程で壊れるのだと思われます。
要するに、先のコマンド(wsl cat utf8.txt | wsl iconv -f utf8 -t sjis > conv2sjis.txt)の、"|"や">"はWSL側(bash)ではなくcmd.exeの機能です。
WSLの中でパイプ処理を完結させればcmd.exe(またはconhost.exe)とのやり取りは最終出力のみなので、壊れないのではないかと考え試してみました。
wslコマンドに引き渡す文字列をダブルクォーテーションで括ってみると、ダブルクォーテーション内がパースされずに巨大な一つのコマンドとみなされるようで、そんなコマンドは無いとエラーになります。
C:\temp>wsl "cat utf8.txt | iconv -f utf8 -t sjis > conv2sjis.txt" /bin/bash: cat utf8.txt | iconv -f utf8 -t sjis > conv2sjis.txt: command not found
そこで、ダブルクォーテーションで括らずに、コマンドプロンプト(cmd.exe)のエスケープ文字"^"をパイプとリダイレクト記号の前につけて再試行してみると、コードページが壊れることなく成功しました。
C:\temp>wsl cat utf8.txt ^| iconv -f utf8 -t sjis ^> conv2sjis.txt C:\temp>chcp 現在のコード ページ: 932 C:\temp>type conv2sjis.txt テストデータ
関連調査1
コードページを65001(UTF-8)にしたうえで、WSLでUTF-8のテキストファイルを表示させると、予想通り文字化けせずに正常に表示されます。
C:\temp>chcp 65001 Active code page: 65001 C:\temp>wsl cat utf8.txt テストデータ
コードページを932(MS932≒Shift-JIS)にしたうえで、WSLでUTF-8のテキストファイルを表示させても、文字化けせずに表示されます。
C:\temp>chcp 932 現在のコード ページ: 932 C:\temp>wsl cat utf8.txt テストデータ
これらの挙動から、conhost.exeまたはWSLがcmd.exeのコードページに応じてWSLの標準入出力の文字コード変換処理を行っていると考えられます。
また、コードページを932でUTF-8文字列をそのまま表示すれば化けますが、パイプでWSLに引き渡して無変換で表示させると化けません。
C:\temp>chcp 932 現在のコード ページ: 932 C:\temp>type utf8.txt 繝・せ繝医ョ繝シ繧ソ C:\temp>type utf8.txt | wsl cat - テストデータ
同じくコードページ932で、Shift-JIS文字列をそのまま表示すれば化けませんが、パイプでWSLに引き渡して無変換で表示させると化けますが、一旦リダイレクトしてファイルに保存すれば正常に元のファイルと完全一致するShift-JISで保存され、もちろん表示もできます。
C:\temp>chcp 932 現在のコード ページ: 932 C:\temp>type sjis.txt テストデータ C:\temp>type sjis.txt | wsl cat - eXgf[^ C:\temp>type sjis.txt | wsl cat - > sjis_through_wsl.txt C:\temp>fc /b sjis.txt sjis_through_wsl.txt ファイル sjis.txt と SJIS_THROUGH_WSL.TXT を比較しています FC: 相違点は検出されませんでした C:\temp>type sjis_through_wsl.txt テストデータ
これらのことから、以下のことが判ります。
- WSLの出力がUTF-8だと、自動でMS932に変換して表示される
- WSLの出力が何であろうと、リダイレクトした場合には無変換でそのまま保存される
関連調査2
C:\temp>chcp 現在のコード ページ: 932 C:\temp>wsl echo テスト テスト C:\temp>wsl echo テスト | more 繝・せ繝・ C:\temp>chcp 現在のコード ページ: 932 C:\temp>wsl echo テスト | wsl more 繝・せ繝 C:\temp>chcp Active code page: 932 C:\temp>wsl echo テスト | wsl more テスト
WSLの出力をパイプでつなぐと、パイプの先がdosコマンドでもWSLでも表示が化けます。
ですが、パイプ先がWSLの場合のみコードページがMS932を装いつつUTF8になるバグが発生することが判ります。
念のため、同じことをマルチバイト文字を使わずに試してみると、WSLのパイプ先がWSLの場合のみコードページがMS932を装いつつUTF8になるバグが発生していることが判りました。
C:\temp>chcp 現在のコード ページ: 932 C:\temp>wsl echo This is a test | more This is a test C:\temp>chcp 現在のコード ページ: 932 C:\temp>wsl echo This is a test | wsl more This is a test C:\temp>chcp Active code page: 932 C:\temp>type sjis.txt �e�X�g�f�[�^
このことから、wslからwslにコマンドプロンプトのパイプでつないだ場合のみコードページが壊れると発生条件は絞れそうです。
結論
- 日本語Windows環境*2でCP932のコマンドプロンプト内でWSLからWSLにパイプで繋ぐと、当該コマンド実行以降はCP932を装いつつ65001(UTF-8)を指定した場合と同様の挙動をする
- そもそもWSLからWSLにcmd.exe側のパイプで繋ぐ意義はないので、WSL内でパイプを完結させる(ダブルクォーテーションではなくパイプ記号の前にハット記号でエスケープする)ことで回避可能
(おまけ)関連調査3
CP932で挙動がおかしくても、コードページ65001(UTF-8)なら大丈夫なのか少しだけ試してみましたが、WSLからDOSコマンドへパイプで繋いだ結果が表示されずおかしい。
C:\temp>chcp Active code page: 65001 C:\temp>wsl echo This is a test | more This is a test C:\temp>wsl echo This is a test | wsl more This is a test C:\temp>wsl echo テスト | more C:\temp>wsl echo テスト | wsl more テスト
かと思いきや、パイプ元がDOSコマンドでもWSLの場合と同様に表示されず、これはこれで別なバグが潜んでいそうな。
C:\temp>chcp Active code page: 65001 C:\temp>echo テスト テスト C:\temp>echo テスト | more
というわけで、日本語を含めマルチバイト文字を扱うなら、下手にコードページは変えずにCP932のままにして、WSLからWSLにパイプしないよう気を付けるだけの方が良さそうだと思います。
以上。