PowerShell で日本語が化ける ── 読み・書き・受け渡しで別々に潰す
目次
- そもそもの元凶
- 1. 読むとき ── Get-Content:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;} が化ける
- 2. 書くとき ── BOM 問題
- 3. 別プロセスに渡すとき ── -Body:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;} が化ける
- 4. bat / cmd に日本語を書かない
- まとめ
Windows の PowerShell(5.1)で作業してると、日本語がとにかく化ける。ファイルを読んでも化ける、書いても化ける、他のコマンドに渡しても化ける。「文字コードを UTF-8 にすればいい」で片付くと思いきや、化ける場所ごとに対処が違って毎回ハマるので、まとめて潰しておく。
そもそもの元凶
Windows PowerShell 5.1 のデフォルトエンコーディングは UTF-8 じゃない。OS のロケール依存で、日本語 Windows だと CP932(Shift_JIS 系) になる。一方で今どきのツール(pip・git・node、大半のテキストファイル)は UTF-8 前提。この食い違いが化けの正体。
厄介なのは、化ける場所が1つじゃないこと。「読む」「書く」「別プロセスに渡す」 の3面で、それぞれ別の地雷を踏む。順番に潰す。
PowerShell 7 (pwsh) はデフォルトが UTF-8 なので、これらの多くは自然に消える。ただ Windows に最初から入ってるのは 5.1 で、業務 PC だと 7 を入れられないこともある。ここでは 5.1 前提で書く。
1. 読むとき ── Get-Content が化ける
UTF-8 で保存したファイルを普通に読むと化ける。
Get-Content .\notes.md # 化ける(CP932 として解釈される)
Get-Content .\notes.md -Encoding UTF8 # OK-Encoding UTF8 を付けるだけ。地味だけど、これを忘れて「ファイルは正しいのに表示だけ壊れる、なんで?」と悩むのが定番。
2. 書くとき ── BOM 問題
出力側はもう一段ややこしい。Out-File や Set-Content のデフォルトは UTF-16 LE(BOM つき) で、他のツールが読めないファイルが出来上がる。
"内容" | Out-File .\out.txt # UTF-16 LE。node 等が読めない
"内容" | Out-File .\out.txt -Encoding utf8 # UTF-8 …だが BOM つき-Encoding utf8 で UTF-8 にはなるけど、5.1 の utf8 は BOM つき。BOM が邪魔なツール(一部のパーサ、シェルスクリプト、設定ファイル)だとこれで壊れる。BOM なしで書きたいなら .NET を直で呼ぶのが確実。
$enc = New-Object System.Text.UTF8Encoding($false) # $false = BOM なし
[System.IO.File]::WriteAllText("C:\path\out.txt", $text, $enc)読むときも .NET で明示すると確実。
$text = [System.IO.File]::ReadAllText("C:\path\in.txt", [System.Text.Encoding]::UTF8)スクリプトで大量のファイルを一括置換するときは、Get-Content | Set-Content より、この ReadAllText / WriteAllText の組み合わせの方が、元の日本語を化けさせずに済む。実際これで記事ディレクトリの一括リネームをやった。
3. 別プロセスに渡すとき ── -Body が化ける
意外と気づかないのがこれ。Invoke-RestMethod / Invoke-WebRequest の -Body に日本語混じりの JSON を渡すと、UTF-8 で送られず受け側で化ける。
# 化ける:文字列を渡すと PowerShell が勝手にエンコードして送る
Invoke-RestMethod -Uri $url -Method Post -Body $json -ContentType "application/json"対処は、自分で UTF-8 のバイト列にしてから渡す。
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
Invoke-RestMethod -Uri $url -Method Post -Body $bytes -ContentType "application/json; charset=utf-8"ローカルの TTS サーバに日本語を投げたら合成音声が空(無音)で返ってきて、原因がこれだった。API に届く前に化けていた。
4. bat / cmd に日本語を書かない
最後は文字コードというより運用の話。バッチファイル(.bat / .cmd)に日本語を直書きするのはやめた方がいい。chcp 65001(UTF-8 化)と日本語を混ぜると、行がぶつ切りに壊れて、コマンドが意味不明なエラーで落ちる。
対処はシンプルで、bat / ps1 は純 ASCII に保つ。日本語を出したい・使いたいなら、その部分だけ Python など別ファイルに逃がして、bat からは呼ぶだけにする。
@echo off
chcp 65001 > nul
python run.py :: 日本語は run.py の中に閉じ込めるこうしておくと、ランチャーの bat が文字コードで自壊する事故がなくなる。
まとめ
- 元凶は PS5.1 のデフォルトが CP932、周りは UTF-8、という食い違い
- 読む:
Get-Content -Encoding UTF8 - 書く:BOM なしが欲しければ .NET の
UTF8Encoding($false)+WriteAllText - 渡す:
-BodyはUTF8.GetBytes()でバイト列にしてから - bat:日本語を直書きしない。純 ASCII に保って日本語は別ファイルへ
「UTF-8 にすればいい」の一言で済まないのが Windows のつらいところだけど、化ける場所を分けて考えると、毎回同じ所で悩まなくて済む。PowerShell 7 に上げられるなら、それが結局いちばんの近道ではある。