Open3

WSLでclip.exeに渡した文字列が"文字化け"した際の対処法

kumavalekumavale

WSLでclip.exeを使うと、WSLからWindows側へコマンド実行結果やソースなどをクリップボードにコピーできてめちゃくちゃ便利なのですが、ある日突然文字化けするようになりました・・・

どうやら、wsl 1.1.3の頃からWindowsコマンドを呼び出す際の文字コードの仕様が変わったっぽい


そのため、iconvコマンドを使用して文字コードを変換してやる必要がある。

$ echo 日本語 | iconv -t sjis | clip.exe

Vimのヤンクを渡す際はこんな感じ

.vimrc
augroup Yank
    au!
    autocmd TextYankPost * :call system('iconv -t sjis | clip.exe', @")
augroup END
kumavalekumavale

でもSJISだと 絵文字 が出力できない。

$ echo 🍣 | iconv -t sjis | clip.exe
iconv: illegal input sequence at position 0

utf16にすると上手くいく

何故か、utf16にすると上手くいった。

$ echo 🍣 | iconv -t utf16 | clip.exe

考察

1. メモ帳からコピーしたクリップボードの文字コードを見てみる
  • utf8の「、」をメモ帳からコピー
$ powershell.exe -command get-clipboard | od -x --endian=big
0000000 8141 0d0a

8141が出力されて、SJISだと分かる。(UTF16だと3001

2. clip.exeに渡したクリップボードを見てみる
$ echo 、 | clip.exe && powershell.exe -command get-clipboard | od -x --endian=big
0000000 e380 8145 0d0a

E38081なので、UTF8かな?(45がよく分からない)

3. 因みに、odに渡さずに表示すると文字化け

環境はWindows TerminalなのでUTF8で扱っているはず

$ echo 🍣 | clip.exe && powershell.exe -command get-clipboard
坤
4. iconvでUTF8にする

表示できた

$ echo 🍣 | clip.exe && powershell.exe -command get-clipboard | iconv -t utf8
🍣
5. iconvでUTF16にしてから...

3f81 41ってなんですか...

$ echo 、 | iconv -t utf16 | clip.exe && powershell.exe -command get-clipboard | od -x --endian=big
0000000 3f81 410d 0a0d 0a00

普通に表示はできるんよね...

$ echo 、 | iconv -t utf16 | clip.exe && powershell.exe -command get-clipboard
、
6. 自分なりの結論

パイプせずにクリップボードにある状態では、よく分からない文字コードになっている。
パイプするとSJISかUTF8になる。
iconv -t utf16すると良い感じになる。(何か影響が出るまではこれで行こうと思う)


まだ原因も解決法も全然分からない🥴
文字コードに詳しくなりたい

Mt_SQMt_SQ

はじめまして。
私自身もきちんと理解できていなかったので、問題を整理するつもりでコメントいたします。
「考察」の番号にあわせてコメントを入れました。

1. クリップボードは、システムに表示される(はずの)「文字」を保持しています

ここでは、文字コードを意識する必要はありません。

utf8の「、」をメモ帳からコピー

あくまでもクリップボードは「、」を保持しています。utf8 も メモ帳も無関係です。
「考察1」で見ているのは、powershell.exe が、コマンドの実行結果を標準出力に流したバイト列です。
powershell.exe は、Windows のコマンドプロンプト(以下、DOS窓)上のアプリですので、結果の文字を cp932(いわゆる Shift-JIS)で標準出力に流します。
od コマンドが受け取ったのはDOS窓に「、」を表示させるためのデータです。

2. クリップボードは od コマンドでは確認できません

echo は 文字を標準出力(画面)に流す「wsl のコマンド」です。よって、文字列を utf8 で標準出力に流します。
一方 clip.exe はDOS窓に表示されるはずの「文字」をクリップボードに記録します。
「文字」を確認してみます。
まずは wsl で clip.exe まで。

$ echo 、 | clip.exe

(2023.4.28 19:41 加筆修正しました)
続いてDOS窓上でペーストしてみます。 すでにクリップボードには「文字」が入っているので、文字のペーストが可能なところであれば、どこにペーストしてもいいです。

縲・

「縲・」の「2文字」がクリップボードの中身です。
つまり、 DOS窓の標準出力に「、」の utf8 バイト列E3 80 81を流すと、cp932 のつもりで解釈して上の2文字が表示されるので、この「文字」をコピーしたことになります。
なお、2文字目は中途半端なので画面には「・(中点)」が表示されます。

続いて、powershell.exe が、この2文字を cp932 で標準出力に流します。
cp932 で「縲」はE380、「・(中点)」は8145です。
これを od コマンドで表示させていることになります。

3. clip.exe の段階で違う文字になります

echo 🍣 は、標準出力に utf8 でF0 9F 8D A3を送ります。
お気づきのとおり、このバイト列はDOS窓に、cp932 のF09F(文字割り当てなし) 8DA3(坤)の2文字を表示します。
つまり、クリップボード内はこの2文字になります。

環境はWindows TerminalなのでUTF8で扱っているはず

ここでは、無関係かと思います。

4. iconv は何もしていない

powershell.exe はDOS窓に上の2文字を表示するためにF09F 8DA3のバイト列を送ります。
このバイト列をパイプ経由で wsl の標準出力に流せば、結局F0 9F 8D A3が送られるので 🍣 が表示されます。
結局、iconv は utf8 を受け取り、utf8 を出力していることになります。
よって、cat コマンドでも 🍣 が表示されます。

$ echo 🍣 | clip.exe && powershell.exe -command get-clipboard | cat
🍣

5. 3f81 41の正体は

ここまで、DOS窓での文字表示は cp932 と書いてきましたが、実は utf16le を使うこともできます。
バイト列先頭における BOM(FE FF) で判断し、cp932 の範囲外文字列でも処理できます。
よってiconv -t utf16の出力バイト列は、DOS窓で想定の文字「、」を表示させます。
ただし、clip.exe は BOM と「、」の2文字を保持するので powershell.exe の出力は cp932 で3F(表示できない文字を示す)と8141(、)を od に流します。
これが3f81 41の正体です。

6. パイプに責任はない

パイプせずにクリップボードにある状態では、よく分からない文字コードになっている。
パイプするとSJISかUTF8になる。

パイプもクリップボードも正しく役目を果たしています。
パイプが何かを変換する訳ではありません。
コマンドや標準出力が、どこにどのように作用するかを丁寧に追うと見えてくるかと思います。

wsl で clip.exe を使用する記事はよく目にしますが、Shift-JIS の問題で苦労します。
私の場合は wsl に xsel をインストールして使っています。

$ echo 🍣と🍺 | xsel -bi
$ powershell.exe -command get-clipboard
🍣と🍺

乱筆乱文失礼いたしました。