本題に入る前に、拡張子を整理しておきます。「どのプログラムがそのファイルを読むか」が、そのまま「どの文字コードで保存すべきか」を決めます。この章の内容はすべてここから導かれます。
| 拡張子 | 中身 | 読んで実行するプログラム | この資料での位置づけ |
|---|---|---|---|
.ps1 | PowerShell スクリプト | powershell.exe 5.1pwsh.exe pwsh 7 | 主役。これから書いていくファイル |
.bat.cmd | バッチファイル(DOS 由来) | cmd.exe | ps1 を起動するランチャーとして使う(02.4) |
.js | JavaScript | node.exe | 比較対象。既知の言語として随時登場する |
豆知識.ps1 の「1」は PowerShell 1.0 に由来します。バージョンが 7 になった今も拡張子は .ps1 のままです。
この章では 5.1 pwsh 7 のバッジで挙動の違いを示します。コード例はどちらで実行するかで結果が変わるので、まず自分の環境を確認してください。
$PSVersionTable.PSEdition
| 結果 | 正体 | 起動した実行ファイル |
|---|---|---|
Desktop | 5.1 Windows PowerShell | powershell.exe |
Core | pwsh 7 PowerShell 7 | pwsh.exe |
ここでは判定方法だけを示します。両バージョンがなぜ共存するのか、実行ファイルやプロファイルがどう違うのかは 03. 実行環境と実行ポリシー で扱います。
テキストファイルに保存されているのはバイト(0〜255 の数値)の並びだけです。「あ」という文字そのものが保存されているわけではありません。どのバイト列がどの文字を表すかを決めた対応表が「文字コード」です。
同じ「あ」でも、対応表が違えばバイト列は変わります。
| 名前 | 特徴 | ASCII 互換 | Windows での立場 |
|---|---|---|---|
| Shift-JIS (CP932) | 日本語向けの従来規格。1〜2バイト可変 | ✓ あり | 日本語版 Windows の「ANSI」=既定のコードページ。bat/cmd はいまもこれ |
| UTF-8 | Unicode の可変長表現。ASCII は1バイト、日本語は3バイト | ✓ あり | 現在の事実上の標準。Web・Linux・Node.js はほぼこれ |
| UTF-16LE | Unicode を2バイト単位で表現 | ✗ なし | Windows の内部表現。Out-File の既定だった 5.1 |
ここが最大のポイントです。HTML や HTTP には文字コードを宣言する場所がありますが、プレーンテキストのファイルにはそれがありません。
<!-- HTML なら宣言できる -->
<meta charset="UTF-8">
# HTTP なら宣言できる
Content-Type: text/plain; charset=UTF-8
# ただのテキストファイル(.ps1 .txt .csv)には宣言する場所がない
Write-Host "こんにちは"
宣言がないので、読み込む側は「たぶんこの文字コードだろう」と推測するしかありません。その推測ルールが処理系ごとに違うことが、02.2 で扱う問題の正体です。
BOM(Byte Order Mark)は、ファイルの先頭に置く数バイトの目印です。文字としては表示されず、「このファイルはこの文字コードです」という宣言としてだけ働きます。
| 文字コード | BOM のバイト列 | 備考 |
|---|---|---|
| UTF-8 | EF BB BF | 本来 UTF-8 にバイト順の概念はないが、識別子として使われる |
| UTF-16LE | FF FE | バイト順(リトルエンディアン)を示す本来の用途 |
| Shift-JIS | (なし) | BOM という仕組み自体が存在しない |
つまり BOM は「自己申告できないテキストファイル」に唯一つけられる名札です。次の章で見るとおり、PowerShell はこの名札の有無で挙動を変えます。
InputStreamReader や StreamReader に文字コードを渡す、あの場面です)。PowerShell もまったく同じ構造で、内部は .NET の文字列(UTF-16)、境界でエンコーディングの指定が必要になります。PowerShell がスクリプトファイルを読むとき、BOM があればそれに従います。問題はBOM がなかったときです。ここで 5.1 と 7 は正反対の推測をします。
| バージョン | BOM がないときの推測 |
|---|---|
| 5.1 Windows PowerShell | ANSI(日本語環境では CP932)だと見なす |
| pwsh 7 PowerShell 7 | UTF-8 だと見なす |
この違いを保存形式ごとに整理すると、次のようになります。
図の3行目に注目してください。Shift-JIS の ps1 は 5.1 では動くのに、7 では壊れます。
どちらも「自分の環境では動く」ため、渡した側は気づけません。BOM 付き UTF-8 で統一しておけば、両方とも起きません。
右下のステータスバーに現在のエンコーディング(UTF-8 など)が表示されています。クリックして [エンコード付きで保存]→[UTF-8 with BOM]を選びます。
ここに同じ名前で結果が違うという罠があります。
# 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 の意味がバージョンで変わります。UTF8 が BOM 付きを意味しますが、pwsh 7 では utf8 は BOM なしです。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
「改行」も画面上は見えませんが、実体はバイトです。歴史的な経緯から3種類が存在します。
ここは誤解されやすい点なので明確にしておきます。PowerShell スクリプト自体は LF 改行でも問題なく動作します。5.1 でも 7 でも同じです。
ではなぜ CRLF に揃えるのか。理由は3つあります。
| 理由 | 内容 | 重要度 |
|---|---|---|
| ① 混在の防止 | 1つのファイル内で CRLF と LF が混ざると、diff が全行変更として表示され、変更箇所が追えなくなる | 高 |
| ② bat / cmd との統一 | bat は CRLF が事実上必須(後述)。同じプロジェクト内で改行を分けると管理が煩雑になる | 中 |
| ③ Windows 標準ツール | 古いエディタや一部のツールは LF のみのファイルを1行として表示する | 低 |
goto のラベル解決や行の連結で予期しない失敗をすることがあります。ps1 と違い、ここは「動くこともある」ではなく「揃えるべき」です。
core.autocrlf に注意Git には、コミット時とチェックアウト時に改行コードを自動変換する設定があります。これが意図せず有効だと、手元では CRLF なのにリポジトリ上は LFという状態になります。
| 設定値 | コミット時 | チェックアウト時 | この資料での推奨 |
|---|---|---|---|
true | CRLF → LF に変換 | LF → CRLF に変換 | Windows のみのチームなら可 |
input | CRLF → LF に変換 | 変換しない | — |
false | 変換しない | 変換しない | 推奨ファイルの実体をそのまま扱える |
より確実なのは .gitattributes で拡張子ごとに固定する方法です。
# .gitattributes
*.ps1 text eol=crlf
*.bat text eol=crlf
*.cmd text eol=crlf
Node.js のプロジェクトでは LF が標準です。Prettier や ESLint の既定も LF で、CRLF だと警告が出ます。同じ PC で PowerShell 資材と Node.js 資材の両方を扱うときは、拡張子ごとに使い分けるのが現実的です。
CRLF または LF と表示されています。ここをクリックすると切り替えられます。まずは自分が編集中のファイルがどちらなのかを見る習慣をつけてください。
冒頭の「この資料に出てくるファイルの種類」で触れた3種類を、ここまでに分かった文字コード・改行の観点で整理し直します。
| 拡張子 | 実行するもの | ダブルクリック | 文字コード | 改行 |
|---|---|---|---|---|
.ps1 | PowerShell | 実行されない編集用に開く | BOM 付き UTF-8 | CRLF |
.bat | cmd.exe | 実行される | Shift-JIS | CRLF |
.cmd | cmd.exe | 実行される | Shift-JIS | CRLF |
.bat と .cmd はほぼ同じです。.cmd のほうが新しく、内部コマンド実行後の ERRORLEVEL の扱いがわずかに整理されています。新規に作るなら .cmd で構いません。
chcp 65001 で UTF-8 に切り替える手もありますが、実行環境ごとに事情が変わるため、素直に Shift-JIS で保存するほうが確実です。ps1 と bat で文字コードが違うのはここが理由です。
これは仕様です。.ps1 の既定の関連付けは「実行」ではなく「編集」になっています。スクリプトを不用意にダブルクリックして実行してしまう事故を防ぐための設計です。
加えて実行ポリシーという別の制限もあり、既定の設定ではスクリプトファイルの実行自体がブロックされます(詳しくは 03 章)。
そこで、ダブルクリックで動かしたい ps1 には、同名の cmd ランチャーを添えます。
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0foo.ps1"
pause
| 要素 | 意味 | 省くとどうなるか |
|---|---|---|
@echo off | コマンド自体の表示を抑制する | 実行するコマンドが画面に出て見づらい |
%~dp0 | この bat があるフォルダの絶対パス(末尾に \ が付く) | ダブルクリック時のカレントが別の場所だと ps1 が見つからない |
-NoProfile | プロファイル(起動時スクリプト)を読み込まない | 起動が遅くなり、個人設定の影響で挙動が変わる |
-ExecutionPolicy Bypass | この実行に限り実行ポリシーを無視する | 既定のポリシーだと実行が拒否される |
-File | スクリプトファイルを実行する指定 | — |
pause | キー入力待ちで画面を保持する | ウィンドウが一瞬で閉じてエラーが読めない |
%~dp0 は必ず付けてください。"%~dp0foo.ps1" と書けば、bat 自身の隣にある ps1 を確実に指せます。パスにスペースが含まれる場合に備えて、ダブルクォートで囲むことも忘れないでください。
実行ファイル名を変えるだけです。
@echo off
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0foo.ps1"
pause
| 実行ファイル | 起動するもの | 導入 |
|---|---|---|
powershell.exe | 5.1 Windows PowerShell | Windows に標準搭載 |
pwsh.exe | pwsh 7 PowerShell 7 | 別途インストールが必要 |
配布先に 7 が入っている保証がないなら、powershell.exe のままにしておくのが安全です。
| 対象 | 文字コード | 改行 | 理由 |
|---|---|---|---|
.ps1 | BOM 付き UTF-8 | CRLF | BOM がないと 5.1 は ANSI と誤認する。BOM 付き UTF-8 は 5.1 と 7 の両方で正しく読める唯一の形式 |
.bat / .cmd | Shift-JIS | CRLF | cmd.exe が現在のコードページ(日本語環境では 932)で解釈するため |
-Encoding UTF8 の意味がバージョンで違う。7 で BOM を付けるなら utf8BOM を明示する-ExecutionPolicy Bypass が何を回避しているのか、そもそもなぜ既定でスクリプトが実行できないのかを理解すると、環境構築で迷わなくなります。