07. ファイル・テキスト操作

02 章の文字コードが、ここで -Encoding として効いてくる

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

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

「0 件が返る」「文字化けする」の大半はこの章の内容です。
ファイル操作はコマンド自体が単純なので、つまずくのは指定の仕方です。この章では動くコードよりも、動かないときに何を疑うかを中心に扱います。

07.1 パスの組み立て

文字列結合でパスを作らない

文字列結合 Join-Path "$dir" + "\" + "$name" Join-Path $dir $name $dir が末尾に \ を持っていると C:\work\\a.txt 区切りが二重になる。動く場合もあり発見が遅れる 末尾の \ の有無を吸収してくれる C:\work\a.txt Linux 上では / を使うので移植性も上がる 3階層以上もまとめて渡せる(PowerShell 7) Join-Path 'C:\a' 'b' 'c' → C:\a\b\c
図 07.1 — Join-Path は区切り文字の重複を吸収する

基準となる場所を間違えない

変数指すもの使いどころ
$PSScriptRootこのスクリプトがあるフォルダ推奨同梱ファイルを読む
$PWD / Get-Locationカレントディレクトリユーザーが今いる場所を対象にする
$HOMEユーザーのホーム設定ファイルの置き場所
$env:TEMP一時フォルダ作業用ファイル
# スクリプトと同じ場所にある設定ファイルを読む(定番)
$config = Join-Path $PSScriptRoot 'config.json'
ダブルクリックで実行したときのカレントは、スクリプトの場所とは限りません。
ショートカット経由やタスクスケジューラ経由では、まったく別のフォルダがカレントになります。同梱ファイルを読むときは必ず $PSScriptRoot を使ってください。02 章の cmd ランチャーで %~dp0 を使ったのと同じ理由です。

カレントの実行ファイルは .\ を明示する

# ✗ 環境設定によっては見つからない
mytool.exe

# ✓ カレントを明示する
.\mytool.exe

PowerShell はカレントディレクトリを自動では探しません(意図しないプログラムの実行を防ぐため)。cmd.exe とは挙動が違う点です。

パス操作のコマンド

コマンド用途
Join-Path連結するJoin-Path $dir 'a.txt'
Split-Path分解するSplit-Path $p -Parent / -Leaf
Test-Path存在を確認するTest-Path $p -PathType Leaf
Resolve-Path絶対パスにする(存在必須)Resolve-Path .\docs
Convert-Path絶対パスにする(文字列で返る)Convert-Path .\docs
PowerShell が初めての人へ
Test-Pathファイルでもフォルダでも True を返します。区別したいときは -PathType Leaf(ファイル)か -PathType Container(フォルダ)を付けてください。
「ファイルがあるはずなのに条件分岐がおかしい」というとき、同名のフォルダを掴んでいることがあります。

07.2 Get-ChildItem — 0 件になる3つの理由

検証に使うファイル構成

以降の例はすべて、次の構成に対して実行した実測値です。

$base\
├── docs-a\
│   ├── a.txt
│   └── a.log
├── docs-b\
│   └── b.txt
└── other\
    └── c.txt
数え方該当するファイル件数
.txt をすべてa.txt / b.txt / c.txt3 件
docs-* の下の .txt だけa.txt / b.txt2 件
other の下c.txt1 件

「docs-a と docs-b の中の .txt だけが欲しい」= 2 件が正解という前提で読んでください。a.logc.txt は除外されるべきものです。

理由① パスのワイルドカードと -Filter を併用した 最頻出

これが最も多い原因です。エラーは出ず、静かに 0 件を返します

docs-a\a.txt ・ docs-a\a.log ・ docs-b\b.txt ・ other\c.txt がある状態で Get-ChildItem "docs-*" -Filter "*.txt" 0 件 (取れない) Get-ChildItem "docs-*\*.txt" 2 件 a.txt / b.txt Get-ChildItem $base -Recurse -Filter "*.txt" 3 件 c.txt も混ざる ↑ | Where-Object { $_.Directory.Name -like 'docs-*' } 絞り込みは Where-Object に任せる 2 件 これが正解 -Filter はプロバイダに渡されるため、ワイルドカードで展開されたパスとは噛み合わない
図 07.2 — 実測値。①だけが 0 件になる。エラーが出ないため原因に気づきにくい
# ✗ 0 件になる
Get-ChildItem "$base\docs-*" -Filter '*.txt' -File

# ✓ 親フォルダを指定して再帰し、絞り込みは Where-Object で行う
Get-ChildItem $base -Recurse -Filter '*.txt' -File |
    Where-Object { $_.Directory.Name -like 'docs-*' }

理由② -Include-Recurse なしで使った

Get-ChildItem $base -Include '*.txt'              # → 0 件(何も返らない)
Get-ChildItem $base -Recurse -Include '*.txt'     # → 3 件(a.txt / b.txt / c.txt)
Get-ChildItem "$base\*" -Include '*.txt'          # → これでも動く
-Include はパスの末尾に対して働きます。
-Recurse か、末尾に \* が付いたパスが必要です。単にフォルダを指定しただけでは何にも一致しません。
迷ったら -Filter を使ってください。-Filter のほうが高速で(ファイルシステム側で絞るため)、この落とし穴もありません。ただしパターンは1つしか書けません。複数拡張子を扱うときだけ -Include の出番です。
観点-Filter-Include
速度速いファイルシステムが絞るPowerShell 側で絞る
パターン数1つだけ複数指定できる
条件特になし-Recurse\* が必要
ワイルドカードパス併用不可併用できる

理由③ ファイル名に [ ] が含まれていた

-Path はワイルドカードを解釈します。角括弧は「いずれか1文字」を意味するため、実在するファイルが見つかりません。

report[2026].txt が実在する状態で Get-ChildItem -Path "report[2026].txt" 0 件 Get-ChildItem -LiteralPath "report[2026].txt" 1 件 [2026] は「2 か 0 か 6 のいずれか1文字」を意味する report2.txt / report0.txt / report6.txt これらを探しに行くので 0 件になる。-Path では作成すらできない
図 07.3 — 角括弧を含む名前は -LiteralPath でしか扱えない
変数から受け取ったパスには -LiteralPath を使ってください。
自分で書いたリテラルなら文字を把握できますが、ユーザー入力やファイル一覧から来たパスに何が含まれているかは分かりません。角括弧のほか * ? も同じ問題を起こします。
日付やバージョンを角括弧で囲む命名は実際によくあるため、遭遇率は低くありません。

よく使う指定

Get-ChildItem $dir -File                        # ファイルだけ
Get-ChildItem $dir -Directory                   # フォルダだけ
Get-ChildItem $dir -Recurse -Depth 2            # 深さを制限する
Get-ChildItem $dir -Force                       # 隠しファイルも含める
Get-ChildItem $dir -Recurse -ErrorAction SilentlyContinue   # アクセス不可を無視
PowerShell が初めての人へ
-Recurse で深い階層を辿ると、アクセス権のないフォルダでエラーが大量に出ることがあります。処理自体は続きますが、画面が赤く埋まって本来のエラーが埋もれます。
-ErrorAction SilentlyContinue を付けると黙って読み飛ばします。エラーを後で確認したいときは -ErrorVariable で受け取れます(08 章)。

07.3 読み書きと文字コード

02 章の話がここで効いてくる

02 章で「ファイルは文字コードを自己申告しない」と学びました。読み書きのたびに指定が必要になるのがその帰結です。しかも既定値が 5.1 と 7 で違います

既定のエンコーディング — 実測値

「あ」1文字を書き出したときの実際のバイト列です。

書き出し方 Windows PowerShell 5.1 PowerShell 7 Set-Content(既定) ANSI(CP932) 82 A0 UTF-8(BOM なし) E3 81 82 Out-File(既定) UTF-16LE(BOM 付き) FF FE 42 30 UTF-8(BOM なし) E3 81 82 -Encoding UTF8 UTF-8(BOM 付き) EF BB BF E3 81 82 UTF-8(BOM なし) E3 81 82 同じ -Encoding UTF8 が、5.1 では BOM 付き、7 では BOM なし 7 で BOM を付けるには utf8BOM を明示する 結論 — 読み書きの両方で -Encoding を必ず明示する
図 07.4 — 5.1 は3種類バラバラ、7 はすべて UTF-8。既定に頼ると移植時に壊れる
5.1 の既定はコマンドごとに違います
Set-Content は ANSI、Out-File> は UTF-16LE です。同じスクリプトの中で書き方を変えると、出力ファイルの文字コードが混在します。7 ではすべて UTF-8 に統一されました。
だからこそ、既定に頼らず毎回 -Encoding を書くのが唯一の安全策です。

両方で同じ結果になる書き方

# 読む(BOM があればそちらが優先される)
$text = Get-Content .\data.txt -Encoding UTF8 -Raw

# 書く
$text | Set-Content .\out.txt -Encoding UTF8      # 5.1: BOM 付き / 7: BOM なし
やりたいこと5.1pwsh 7
BOM 付き UTF-8 で書く-Encoding UTF8-Encoding utf8BOM
BOM なし UTF-8 で書く標準では不可(.NET を直接使う)-Encoding utf8
Shift-JIS で書く-Encoding Default-Encoding Shift_JIS

Get-Content は既定で「行の配列」を返す

書き方戻り値用途
Get-Content a.txtObject[](1行1要素)行ごとに処理する
Get-Content a.txt -RawString(改行含む1つの文字列)正規表現・JSON のパース
Get-Content a.txt -TotalCount 10先頭 10 行大きなファイルの先頭確認
Get-Content a.txt -Tail 10末尾 10 行ログの確認
Get-Content a.txt -Wait追記を待ち受けるtail -f 相当
JSON を読むときは -Raw が必須です。
-Raw なしだと行ごとの配列になり、ConvertFrom-Json が正しく解釈できません。「JSON の解析に失敗する」の原因はほぼこれです。
# ✗ 行の配列が渡ってしまう
Get-Content .\data.json | ConvertFrom-Json

# ✓
Get-Content .\data.json -Raw | ConvertFrom-Json
PowerShell が初めての人へ
巨大なログを扱うときは Get-Content の既定(行の配列)が有利です。パイプラインに1行ずつ流れるので、10GB のファイルでもメモリを使い切りません。
逆に -Raw全体を1つの文字列としてメモリに載せます。設定ファイルのような小さいファイル専用と考えてください。

07.4 CSV と JSON

CSV — 表形式のデータ

# 読む(1行が1オブジェクトになる)
$rows = Import-Csv .\data.csv -Encoding UTF8

# 書く
$rows | Export-Csv .\out.csv -Encoding UTF8 -NoTypeInformation
5.1 では -NoTypeInformation を必ず付けてください。
付けないと 1 行目に #TYPE System.Management.Automation.PSCustomObject という行が入り、Excel や他のツールで開いたときに列がずれますpwsh 7 では既定で付かなくなったため不要ですが、書いておけば両方で正しく動きます

読み込んだ値はすべて文字列

$rows = Import-Csv .\sales.csv
$rows[0].Amount + 1        # → "1000" + 1 = "10001"  ✗ 文字列結合

[int]$rows[0].Amount + 1   # → 1001                  ✓

04 章で見た「左辺の型が演算を決める」がここで効いてきます。CSV から読んだ数値は必ず明示的に変換してください。

JSON

# 読む
$config = Get-Content .\config.json -Raw | ConvertFrom-Json

# 書く
$data | ConvertTo-Json -Depth 10 | Set-Content .\out.json -Encoding UTF8

-Depth の既定は 2 データが消える

入れ子が3階層を超えると、そこから先が文字列に置き換わります

@{ L1 = @{ L2 = @{ L3 = @{ L4 = 'ここまで届くか' } } } } ConvertTo-Json(既定 -Depth 2) {"L1":{"L2":{"L3":"System.Collections.Hashtable"}}} ConvertTo-Json -Depth 10 {"L1":{"L2":{"L3":{"L4":"ここまで届くか"}}}} 深い部分が "System.Collections.Hashtable" という文字列に化ける 警告は出るが処理は続行される。書き出したファイルを開くまで気づかない
図 07.5 — 実測結果。既定のままだと3階層目から先が失われる
-Depth は最初から大きめに書いてください。
-Depth 10-Depth 100 を付けておけば、後から構造が深くなっても壊れません。警告は出ますが処理は止まらないため、出力ファイルを開くまで気づけない種類の事故です。
pwsh 7 でも既定は 2 のままです。バージョンを上げても解決しません。

JSON を読むと PSCustomObject になる

$config = Get-Content .\config.json -Raw | ConvertFrom-Json
$config.database.host        # ドットでたどれる

# ハッシュテーブルとして受け取りたい場合
$hash = Get-Content .\config.json -Raw | ConvertFrom-Json -AsHashtable   # pwsh 7

-AsHashtablepwsh 7 で追加されました。キーを動的に扱いたいときに便利です。

形式ごとの使い分け

形式向いているもの注意点
CSV表形式・Excel との連携すべて文字列になる。-NoTypeInformation
JSON入れ子のある設定・API との連携-Depth、読むとき -Raw
CLIXMLPowerShell オブジェクトの完全な保存PowerShell 専用。他言語から読めない
# オブジェクトをそのまま保存して復元する(型情報も保たれる)
Get-Process | Export-Clixml .\proc.xml
$procs = Import-Clixml .\proc.xml
PowerShell が初めての人へ
Export-ClixmlPowerShell のオブジェクトを型ごと保存できる形式です。JSON と違って日付が DateTime のまま復元されるので、スクリプト間でデータを受け渡すときに便利です。
ただし他の言語からは読めません。外部と共有するなら JSON か CSV、PowerShell 内で完結するなら CLIXML、と使い分けてください。

この章のまとめ

症状疑うところ
0 件が返る(エラーなし)ワイルドカードパスと -Filter の併用 / -Include-Recurse が無い / 名前に [ ]
文字化けする読み書きの -Encoding を省略している。5.1 と 7 で既定が違う
JSON の解析に失敗するGet-Content-Raw が無い
JSON の一部が文字列に化けるConvertTo-Json-Depth(既定 2)
CSV の列がずれる5.1 で -NoTypeInformation を付けていない
CSV の数値がおかしいすべて文字列。[int] で明示的に変換する
スクリプトが同梱ファイルを見つけられないカレント依存。$PSScriptRoot を使う

覚えておく3点

  1. 0 件はエラーではないGet-ChildItem が空を返したら、まず指定の仕方を疑う。ワイルドカードパスと -Filter の併用が最頻出
  2. 読み書きの両方で -Encoding を明示する。既定値は 5.1 でコマンドごとにバラバラ、7 では UTF-8。既定に頼ると移植時に壊れる
  3. -Raw-Depth を忘れない。JSON を読むときは -Raw、書くときは -Depth 10。どちらも欠けると静かにデータが壊れる
次章の予告 — 08. エラー処理とデバッグ
この章で扱った失敗の多くはエラーにならずに 0 件や空文字列を返します。次章では、そもそもPowerShell のエラーが2種類あること、try/catch が効かないエラーがあること、そして外部コマンドの失敗をどう検知するかを扱います。