02. ファイル形式と文字コード

なぜ ps1 は「BOM 付き UTF-8 + CRLF」でなければならないのか

📅 作成: 2026-08-22 / 更新: 2026-08-24 / 対象: Windows PowerShell 5.1 + PowerShell 7

この章で何ができるようになるか

この章は「決まりごと」の章です。
「BOM 付き UTF-8 で CRLF」という結論だけなら1行で済みます。しかし理由を知らない規則は必ず忘れられ、いつか破られます。この章ではなぜそうなるのかを先に説明し、規則は結論として最後に置きます

その前に — この資料に出てくるファイルの種類

本題に入る前に、拡張子を整理しておきます。「どのプログラムがそのファイルを読むか」が、そのまま「どの文字コードで保存すべきか」を決めます。この章の内容はすべてここから導かれます。

拡張子中身読んで実行するプログラムこの資料での位置づけ
.ps1PowerShell スクリプトpowershell.exe 5.1
pwsh.exe pwsh 7
主役。これから書いていくファイル
.bat
.cmd
バッチファイル(DOS 由来)cmd.exeps1 を起動するランチャーとして使う(02.4)
.jsJavaScriptnode.exe比較対象。既知の言語として随時登場する
ファイル 読むプログラム 期待する文字コード .ps1 powershell.exe / pwsh.exe BOM 付き UTF-8 .bat / .cmd cmd.exe Shift-JIS .js node.exe BOM なし UTF-8
図 02.0 — 読むプログラムが違えば、期待する文字コードも違う。この章はこの表の右端を導く

豆知識.ps1 の「1」は PowerShell 1.0 に由来します。バージョンが 7 になった今も拡張子は .ps1 のままです。

この図の色は資料全体で統一します。
以降の図でも 青=ps1橙=bat・cmd緑=js を保ちます。色を見ればどのファイルの話かが分かります。

いま動いているバージョンを確認する

この章では 5.1 pwsh 7 のバッジで挙動の違いを示します。コード例はどちらで実行するかで結果が変わるので、まず自分の環境を確認してください。

$PSVersionTable.PSEdition
結果正体起動した実行ファイル
Desktop5.1 Windows PowerShellpowershell.exe
Corepwsh 7 PowerShell 7pwsh.exe

ここでは判定方法だけを示します。両バージョンがなぜ共存するのか、実行ファイルやプロファイルがどう違うのかは 03. 実行環境と実行ポリシー で扱います。

02.1 文字コードとは何か — バイトと文字の対応表

ファイルの中身はバイトの並びでしかない

テキストファイルに保存されているのはバイト(0〜255 の数値)の並びだけです。「あ」という文字そのものが保存されているわけではありません。どのバイト列がどの文字を表すかを決めた対応表が「文字コード」です。

同じ「あ」でも、対応表が違えばバイト列は変わります。

「あ」 Shift-JIS (CP932) 82 A0 2 バイト UTF-8 E3 81 82 3 バイト UTF-16LE 42 30 2 バイト
図 02.1 — 同じ1文字でも、文字コードが違えばバイト列は別物になる

Windows で登場する主な文字コード

名前特徴ASCII 互換Windows での立場
Shift-JIS
(CP932)
日本語向けの従来規格。1〜2バイト可変✓ あり日本語版 Windows の「ANSI」=既定のコードページ。bat/cmd はいまもこれ
UTF-8Unicode の可変長表現。ASCII は1バイト、日本語は3バイト✓ あり現在の事実上の標準。Web・Linux・Node.js はほぼこれ
UTF-16LEUnicode を2バイト単位で表現✗ なしWindows の内部表現。Out-File の既定だった 5.1
「ANSI」という呼び名に注意。
Windows が言う「ANSI」は特定の文字コードの名前ではなく、その環境の既定のコードページを指します。日本語環境なら CP932(Shift-JIS)、英語環境なら CP1252 です。つまり同じファイルが環境によって別の文字コードとして読まれます。この曖昧さが後のトラブルの温床になります。

問題の核心 — テキストファイルは文字コードを自己申告しない

ここが最大のポイントです。HTML や HTTP には文字コードを宣言する場所がありますが、プレーンテキストのファイルにはそれがありません

<!-- HTML なら宣言できる -->
<meta charset="UTF-8">

# HTTP なら宣言できる
Content-Type: text/plain; charset=UTF-8

# ただのテキストファイル(.ps1 .txt .csv)には宣言する場所がない
Write-Host "こんにちは"

宣言がないので、読み込む側は「たぶんこの文字コードだろう」と推測するしかありません。その推測ルールが処理系ごとに違うことが、02.2 で扱う問題の正体です。

BOM — 唯一の明示的な手がかり

BOM(Byte Order Mark)は、ファイルの先頭に置く数バイトの目印です。文字としては表示されず、「このファイルはこの文字コードです」という宣言としてだけ働きます。

文字コードBOM のバイト列備考
UTF-8EF BB BF本来 UTF-8 にバイト順の概念はないが、識別子として使われる
UTF-16LEFF FEバイト順(リトルエンディアン)を示す本来の用途
Shift-JIS(なし)BOM という仕組み自体が存在しない

つまり BOM は「自己申告できないテキストファイル」に唯一つけられる名札です。次の章で見るとおり、PowerShell はこの名札の有無で挙動を変えます。

PowerShell が初めての人へ
Java・C# の経験がある方へ — それらの言語では、文字列はメモリ上では常に UTF-16 に統一されていて、エンコーディングを意識するのはファイルやストリームを読み書きする瞬間だけでした(InputStreamReaderStreamReader に文字コードを渡す、あの場面です)。PowerShell もまったく同じ構造で、内部は .NET の文字列(UTF-16)、境界でエンコーディングの指定が必要になります。
違いはその指定を自分で書かない場面があることです。スクリプトファイル自体を PowerShell が読み込むとき、読み手を指定する場所がありません。だから既定値が問題になります。それが 02.2 の主題です。

02.2 なぜ ps1 は BOM 付き UTF-8 なのか

5.1 と 7 で、推測ルールが逆になっている

PowerShell がスクリプトファイルを読むとき、BOM があればそれに従います。問題はBOM がなかったときです。ここで 5.1 と 7 は正反対の推測をします。

バージョンBOM がないときの推測
5.1 Windows PowerShellANSI(日本語環境では CP932)だと見なす
pwsh 7 PowerShell 7UTF-8 だと見なす

この違いを保存形式ごとに整理すると、次のようになります。

ps1 ファイルの保存形式 Windows PowerShell 5.1 の解釈 PowerShell 7 の解釈 UTF-8(BOM 付き) ✓ UTF-8 として読む BOM を見て判定できる ✓ UTF-8 として読む BOM は読み飛ばす UTF-8(BOM なし) ✗ ANSI(CP932)と誤認 日本語が文字化けする ✓ UTF-8 として読む BOM なし=UTF-8 が既定 Shift-JIS(BOM なし) ✓ CP932 として読める 既定が ANSI なので一致する ✗ UTF-8 と誤認 不正バイトとして壊れる 両方で正しく読めるのは「UTF-8(BOM 付き)」だけ
図 02.2 — 5.1 と 7 は「BOM がない場合」の既定解釈が逆。BOM 付き UTF-8 が唯一の共通解
結論:ps1 は BOM 付き UTF-8 で保存する。
これが 5.1 と 7 の両方で正しく読める唯一の形式だからです。どちらか一方しか使わないと決めているなら他の選択肢もありますが、環境が混在した瞬間に事故ります

実務で最も遭遇する事故 — 「pwsh に変えたら化けた」

図の3行目に注目してください。Shift-JIS の ps1 は 5.1 では動くのに、7 では壊れます

どちらも「自分の環境では動く」ため、渡した側は気づけません。BOM 付き UTF-8 で統一しておけば、両方とも起きません。

保存方法

VS Code の場合

右下のステータスバーに現在のエンコーディング(UTF-8 など)が表示されています。クリックして [エンコード付きで保存]→[UTF-8 with BOM]を選びます。

PowerShell から書き出す場合 要注意

ここに同じ名前で結果が違うという罠があります。

# Windows PowerShell 5.1
Set-Content -Path .\a.ps1 -Value $text -Encoding UTF8      # → BOM 付き UTF-8

# PowerShell 7
Set-Content -Path .\a.ps1 -Value $text -Encoding utf8      # → BOM なし UTF-8(!)
Set-Content -Path .\a.ps1 -Value $text -Encoding utf8BOM   # → BOM 付き UTF-8
-Encoding UTF8 の意味がバージョンで変わります。
5.1 では UTF8BOM 付きを意味しますが、pwsh 7 では utf8BOM なしです。7 で BOM を付けたいときは utf8BOM を明示してください。同じスクリプトを両方で動かすなら、この違いは必ず踏みます。

確認方法

BOM が付いているかは、先頭3バイトを見れば分かります。

# 先頭 3 バイトが EF BB BF なら BOM 付き UTF-8
Get-Content .\a.ps1 -AsByteStream -TotalCount 3          # pwsh 7
Get-Content .\a.ps1 -Encoding Byte -TotalCount 3         # 5.1

-AsByteStream は 7 で追加された指定で、5.1 の -Encoding Byte に相当します。pwsh 7

PowerShell が初めての人へ
「文字化け」はデータが壊れた状態ではありません。バイト列は無事なまま、読み方だけを間違えている状態です。だから正しい文字コードで開き直せば元通りに読めます。ただし間違った読み方のまま上書き保存すると、その時点で本当に壊れます。化けた画面を見たら、まず保存せずに開き直してください。

02.3 なぜ CRLF なのか

改行にも3つの流儀がある

「改行」も画面上は見えませんが、実体はバイトです。歴史的な経緯から3種類が存在します。

CRLF 0D 0A Windows / bat / ps1 復帰+改行の2バイト LF 0A Unix / Linux / macOS Node.js・Git の標準 CR 0D 旧 Mac OS(〜9) 現在はほぼ使われない CR = Carriage Return(復帰・行頭に戻る)/ LF = Line Feed(改行・1行送る) タイプライターの「用紙を戻す」「用紙を送る」という2つの動作がそのまま残ったもの
図 02.3 — 改行コードの3種類。CRLF だけが2バイト

正直に言うと、ps1 は LF でも動く

ここは誤解されやすい点なので明確にしておきます。PowerShell スクリプト自体は LF 改行でも問題なく動作します。5.1 でも 7 でも同じです。

ではなぜ CRLF に揃えるのか。理由は3つあります。

理由内容重要度
① 混在の防止1つのファイル内で CRLF と LF が混ざると、diff が全行変更として表示され、変更箇所が追えなくなる
② bat / cmd との統一bat は CRLF が事実上必須(後述)。同じプロジェクト内で改行を分けると管理が煩雑になる
③ Windows 標準ツール古いエディタや一部のツールは LF のみのファイルを1行として表示する
bat / cmd は CRLF が必須と考えてください。
cmd.exe のバッチ解釈は行末に CR があることを前提にした挙動が残っており、LF のみだと goto のラベル解決や行の連結で予期しない失敗をすることがあります。ps1 と違い、ここは「動くこともある」ではなく「揃えるべき」です。

Git を使うなら core.autocrlf に注意

Git には、コミット時とチェックアウト時に改行コードを自動変換する設定があります。これが意図せず有効だと、手元では CRLF なのにリポジトリ上は LFという状態になります。

設定値コミット時チェックアウト時この資料での推奨
trueCRLF → LF に変換LF → CRLF に変換Windows のみのチームなら可
inputCRLF → LF に変換変換しない
false変換しない変換しない推奨ファイルの実体をそのまま扱える

より確実なのは .gitattributes で拡張子ごとに固定する方法です。

# .gitattributes
*.ps1  text eol=crlf
*.bat  text eol=crlf
*.cmd  text eol=crlf

JavaScript / Node.js との違い

Node.js のプロジェクトでは LF が標準です。Prettier や ESLint の既定も LF で、CRLF だと警告が出ます。同じ PC で PowerShell 資材と Node.js 資材の両方を扱うときは、拡張子ごとに使い分けるのが現実的です。

PowerShell が初めての人へ
改行コードは画面に見えないため、問題が起きても原因に気づきにくい種類のトラブルです。VS Code なら右下のステータスバーに CRLF または LF と表示されています。ここをクリックすると切り替えられます。まずは自分が編集中のファイルがどちらなのかを見る習慣をつけてください。

02.4 ps1 / bat / cmd の使い分けとランチャー

3つの拡張子の役割

冒頭の「この資料に出てくるファイルの種類」で触れた3種類を、ここまでに分かった文字コード・改行の観点で整理し直します

拡張子実行するものダブルクリック文字コード改行
.ps1PowerShell実行されない編集用に開くBOM 付き UTF-8CRLF
.batcmd.exe実行されるShift-JISCRLF
.cmdcmd.exe実行されるShift-JISCRLF

.bat.cmd はほぼ同じです。.cmd のほうが新しく、内部コマンド実行後の ERRORLEVEL の扱いがわずかに整理されています。新規に作るなら .cmd で構いません。

bat / cmd が Shift-JIS なのはなぜか。
cmd.exe はファイルを現在のコードページで解釈します。日本語 Windows の既定は 932(Shift-JIS)なので、UTF-8 で保存すると日本語が化けます。chcp 65001 で UTF-8 に切り替える手もありますが、実行環境ごとに事情が変わるため、素直に Shift-JIS で保存するほうが確実です。ps1 と bat で文字コードが違うのはここが理由です。

ps1 はダブルクリックで動かない

これは仕様です。.ps1 の既定の関連付けは「実行」ではなく「編集」になっています。スクリプトを不用意にダブルクリックして実行してしまう事故を防ぐための設計です。

加えて実行ポリシーという別の制限もあり、既定の設定ではスクリプトファイルの実行自体がブロックされます(詳しくは 03 章)。

そこで、ダブルクリックで動かしたい ps1 には、同名の cmd ランチャーを添えます

foo.cmd ダブルクリックで起動 powershell.exe -NoProfile -ExecutionPolicy Bypass foo.ps1 本体の処理 Shift-JIS / CRLF BOM付きUTF-8 / CRLF 同じフォルダに置いた2ファイルで、文字コードが異なる点に注意 ランチャー側は cmd.exe が読むので Shift-JIS、本体は PowerShell が読むので BOM 付き UTF-8
図 02.4 — cmd ランチャーの役割。2つのファイルは文字コードが違う

ランチャーの中身

@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0foo.ps1"
pause
要素意味省くとどうなるか
@echo offコマンド自体の表示を抑制する実行するコマンドが画面に出て見づらい
%~dp0この bat があるフォルダの絶対パス(末尾に \ が付く)ダブルクリック時のカレントが別の場所だと ps1 が見つからない
-NoProfileプロファイル(起動時スクリプト)を読み込まない起動が遅くなり、個人設定の影響で挙動が変わる
-ExecutionPolicy Bypassこの実行に限り実行ポリシーを無視する既定のポリシーだと実行が拒否される
-Fileスクリプトファイルを実行する指定
pauseキー入力待ちで画面を保持するウィンドウが一瞬で閉じてエラーが読めない
%~dp0 は必ず付けてください。
ダブルクリックで実行したときのカレントディレクトリは、必ずしも bat のある場所とは限りません(ショートカット経由やタスクスケジューラ経由では特に)。"%~dp0foo.ps1" と書けば、bat 自身の隣にある ps1 を確実に指せます。パスにスペースが含まれる場合に備えて、ダブルクォートで囲むことも忘れないでください。

PowerShell 7 で動かしたい場合 pwsh 7

実行ファイル名を変えるだけです。

@echo off
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0foo.ps1"
pause
実行ファイル起動するもの導入
powershell.exe5.1 Windows PowerShellWindows に標準搭載
pwsh.exepwsh 7 PowerShell 7別途インストールが必要

配布先に 7 が入っている保証がないなら、powershell.exe のままにしておくのが安全です。

PowerShell が初めての人へ
「ランチャー」とは、本体を起動するためだけの小さな入口ファイルのことです。ここでは ps1 がダブルクリックで動かないという制約を回避するためだけに存在します。中身は2〜3行で、処理そのものは書きません。処理は全部 ps1 側に置いてください。

この章のまとめ

対象文字コード改行理由
.ps1BOM 付き UTF-8CRLFBOM がないと 5.1 は ANSI と誤認する。BOM 付き UTF-8 は 5.1 と 7 の両方で正しく読める唯一の形式
.bat / .cmdShift-JISCRLFcmd.exe が現在のコードページ(日本語環境では 932)で解釈するため

覚えておく3点

  1. テキストファイルは文字コードを自己申告しない。だから読む側が推測し、その推測ルールが 5.1 と 7 で逆になっている
  2. Shift-JIS の ps1 は 5.1 で動いても 7 で壊れる。「pwsh に変えたら化けた」の原因はほぼこれ
  3. -Encoding UTF8 の意味がバージョンで違う。7 で BOM を付けるなら utf8BOM を明示する
次章の予告 — 03. 実行環境と実行ポリシー
この章で何度か出てきた「実行ポリシー」「5.1 と 7 の共存」を正面から扱います。ランチャーに書いた -ExecutionPolicy Bypass が何を回避しているのか、そもそもなぜ既定でスクリプトが実行できないのかを理解すると、環境構築で迷わなくなります。