Windowsのバッチでフォルダー配下を走査し、ファイル名に特定キーワード(例:(BC))を含むファイルだけを抽出して、キーワード別にテキスト保存したいのに、出力ファイルの中身が空になる――そんなときの原因と対処法を、dirコマンドとPowerShellの両面から具体例つきで整理します。
やりたいことを整理する:ポイントは「ファイル名」検索と「括弧つきキーワード」
今回の目的はシンプルに見えて、バッチ(.bat/.cmd)だと詰まりやすい要素がいくつか混ざっています。まずは要件を言語化しておくと、解法の選択が一気に楽になります。
| 項目 | 要件 | よくある誤解・落とし穴 |
|---|---|---|
| 対象 | P: 配下(例:P:\)を再帰的に走査 | アクセス権不足のエラーで途中停止/結果が欠ける |
| 条件 | 「ファイル名」にキーワードを含むものだけ抽出 | findstr で「ファイル内容」を検索してしまう |
| キーワード | (BC) のように括弧を含めて厳密に一致させたい | 括弧を外すと TH などが過剰一致してノイズが増える |
| 出力 | キーワードごとに別々のテキストへ保存 | リダイレクト位置ミスで「空ファイル」だけ量産される |
結論から言うと、「ファイル名に含む」検索だけなら dir(または where)で完結します。将来的に条件が増える/運用が長いなら PowerShell に寄せた方が保守性が高いです。
なぜ「出力ファイルはできるのに中身が空」になりやすいのか
バッチで“それっぽく”書くと、コンソールには echo が出て、出力ファイル自体は作られるのに、中身が空…という状態に陥りがちです。典型パターンを知っておくと、原因切り分けが早くなります。
findstr は基本的に「テキスト検索」であり「ファイル名検索」ではない
findstr は、もともとテキスト(標準入力やファイル内容)から文字列を探すためのコマンドです。「ファイル名にキーワードが入っているか」を判定したいのに、いつの間にか「ファイルの中身にキーワードがあるか」を検索してしまうと、当然ヒットがなく、出力が空になります。
特に、findstr /m は「一致した行を含むファイル名を出す」オプションのため、一見目的に合いそうですが、検索対象は“ファイル内容”です。今回の要件(ファイル名の部分一致)とは別物だと割り切るのが安全です。
パイプ(|)やリダイレクト(>)の位置ミスで「空」を作る
リダイレクトは、その行で実際に標準出力へ出た内容だけをファイルに書きます。たとえば、判定(findstr)だけをファイルへ出力してしまったり、意図せず > file.txt を先に実行してファイルを空で作り直したりすると、「ファイルはできるが中身は空」が簡単に起きます。
さらに、パイプ記号が別の記号に化けていたり、全角になっていたりすると、見た目は似ていてもコマンドがつながらず、結果的に一致判定が常に失敗することもあります。
括弧や特殊文字の扱いで、意図せず構文として解釈される
cmd.exe の世界では、丸括弧 ( ) はコマンドのグルーピングに使われる重要な記号です。引数として渡したつもりの (BC) が、書き方によっては構文に影響する場合があります。対策としては、次のいずれかが鉄板です。
- キーワードやパターンを必ずダブルクォートで囲む
- バッチ内で括弧を直接書く必要があるなら
^(^)のようにエスケープする - どうしても複雑になるなら、ファイル名検索は dir、ロジックはPowerShellに寄せる
ループ内の変数展開(遅延展開)で期待と違う文字列になる
for ループや括弧ブロックの中で %VAR% を使うと、思ったタイミングで展開されず「常に同じ値」「空になる」といった挙動に出会うことがあります。今回のように“キーワードを回しながら出力ファイルを切り替える”処理では、call でサブルーチン化するか、PowerShell に移行すると事故が減ります。
| 症状 | 原因として多いもの | 対策 |
|---|---|---|
| 出力ファイルは作られるが空 | findstr が常に不一致/リダイレクト位置ミス | dir でファイル名検索に切り替え、最小構成で動作確認 |
| コンソールには表示されるが保存されない | echo と書き出しの対象が別、> が別行にある | 「実際に出したい行」を直接リダイレクトする |
| キーワードに括弧が入ると動かない | 括弧が構文に干渉/クォート不足 | パターンを “*(BC)*” のようにクォートして渡す |
解決策B:用途が単純なら dir コマンドで完結させる
「指定キーワードを“ファイル名に含む”ファイルを列挙したい」だけなら、cmdの標準コマンドである dir が最短です。括弧もファイル名の文字として扱えるので、キーワードに (BC) のような文字列を含めても問題になりにくいのが利点です。
まずは1キーワードで動作確認する
いきなりバッチ化する前に、コマンドプロンプトで1本だけ打って、期待どおりにパスが出るかを確認します(ここで出ないなら、スクリプトの問題ではなく「実際にファイル名が存在しない」「ドライブが違う」などの前提を疑えます)。
dir /b /s /a:-d "P:\*(BC)*" > "C:\temp\(BC).txt"
オプションの意味は次のとおりです。
| 指定 | 意味 | 今回うれしいポイント |
|---|---|---|
| /b | bare形式(余計なヘッダーやサイズ表示を出さない) | そのまま一覧として保存しやすい |
| /s | サブフォルダーも含めて再帰検索 | P:配下をまとめて走査できる |
| /a:-d | ディレクトリを除外(ファイルのみ) | 結果が「ファイル名一覧」に揃う |
権限のないフォルダーが混ざる環境では、エラーメッセージが大量に出て見づらくなることがあります。その場合は次のように 2>nul を付けて抑制します。
dir /b /s /a:-d "P:\*(BC)*" 2>nul > "C:\temp\(BC).txt"
複数キーワードを回して「キーワード別に保存」するバッチ例
次は、キーワードをリスト化し、キーワードごとに出力ファイルを作る定番形です。キーワードに括弧が含まれていても、ファイル検索パターンをクォートしておけば安定します。
@echo off
setlocal EnableExtensions
rem 走査対象(末尾の \ は付けても付けなくてもOK)
set "ROOT=P:\"
rem 出力先フォルダー
set "OUT=C:\temp\keyword_list"
if not exist "%OUT%" mkdir "%OUT%"
rem キーワード一覧(必要に応じて追加)
for %%K in ("(BC)" "(TH)" "(XY)") do (
rem /a:-d でファイルのみに限定
rem 2>nul でアクセス拒否のエラーを抑制
dir /b /s /a:-d "%ROOT%*%%~K*" 2>nul > "%OUT%\%%~K.txt"
)
echo Done.
endlocal
この方法のメリットは、ロジックが単純で壊れにくいことです。findstr のような判定処理を挟まないので、「出力が空になる」原因を自分で増やしません。
キーワードを外部ファイルで管理する(運用向け)
キーワードが増える/担当者が増えると、バッチを直接編集する運用はミスが起きます。そこで、キーワードを keywords.txt(1行1キーワード)に分けておくと、更新が安全になります。
keywords.txt の例
(BC)
(TH)
(2025)
(納品)
バッチ例(keywords.txt を読み込む)
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ROOT=P:"
set "OUT=C:\temp\keyword_list"
set "KEYFILE=%~dp0keywords.txt"
if not exist "%OUT%" mkdir "%OUT%"
for /f "usebackq delims=" %%K in ("%KEYFILE%") do (
if not "%%K"=="" (
dir /b /s /a:-d "%ROOT%*%%K*" 2>nul > "%OUT%%%K.txt"
)
)
echo Done.
endlocal
ポイントは delims= を付けて、キーワードにスペースが含まれても行全体を読み取れるようにすることです(例:(BC) 重要 のようなキーワードを扱う場合に効きます)。
dir 方式の注意点:出力ファイル名に使えない文字がある
今回の (BC) のような括弧はWindowsのファイル名に使えるので問題ありません。ただし、キーワードに : * ? " < > | などが入ると、出力ファイル名として成立しません。そういう運用が想定されるなら、出力ファイル名だけ別のルール(連番、または置換)にしておくのが安全です。
解決策A:PowerShell に切り替える(拡張しやすく、保守性が高い)
「バッチは古くて扱いづらい」という指摘が出やすいのは、文字列処理とエラー処理が増えた瞬間に可読性が落ちるからです。PowerShell なら、ファイル列挙・条件判定・保存が一つの言語で完結し、後から要件が増えても崩れにくくなります。
ファイル名検索の基本:Get-ChildItem + -Filter か -like
PowerShell の Select-String はファイル内容検索です。今回の目的が「ファイル名」なら、Name(ファイル名)や FullName(フルパス)に対して条件を掛けます。
最小構成(キーワード別に保存)
$root = 'P:\'
$out = 'C:\temp\keyword_list'
$keywords = @('(BC)', '(TH)', '(XY)')
if (-not (Test-Path $out)) { New-Item -ItemType Directory -Path $out | Out-Null }
foreach ($kw in $keywords) {
# -Filter はワイルドカード検索(* ? [] が特殊、括弧は通常文字)
$files = Get-ChildItem -Path $root -Recurse -File -Filter "*$kw*" -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty FullName
$files | Out-File -FilePath (Join-Path $out "$kw.txt") -Encoding UTF8
}
この形なら「括弧つきキーワード」をそのまま扱えます。ファイル名一致はワイルドカード(*)ベースなので、(TH) を指定すれば TH だけで過剰一致することも避けられます。
-like と -match の違い:括弧を扱うなら -like が安全
PowerShell には -like(ワイルドカード)と -match(正規表現)があります。括弧は正規表現だと意味を持つため、-match を使う場合はエスケープが必要です。
| 演算子 | 判定方式 | (BC) をそのまま渡した場合 | おすすめ用途 |
|---|---|---|---|
| -like | ワイルドカード(* ? []) | 括弧は通常文字として扱われる | ファイル名の部分一致に最適 |
| -match | 正規表現 | 括弧はグルーピングなので意図とズレやすい | 複雑なパターンが必要なとき |
-match で“文字としての括弧”を扱う例
$kw = '(BC)'
$pattern = [regex]::Escape($kw) # \(BC\) に変換される
Get-ChildItem -Path 'P:\' -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match $pattern } |
Select-Object -ExpandProperty FullName
キーワードが多い場合:走査は1回にして、結果を振り分ける
キーワードが数個なら「キーワードごとに -Filter で再帰検索」を繰り返しても現実的です。一方、キーワードが数十〜数百になると、フォルダー走査を何度も繰り返すのは非効率になりがちです。そんなときは、ファイル列挙は1回にして、メモリ上で振り分ける方が総時間が読みやすくなります。
$root = 'P:\'
$out = 'C:\temp\keyword_list'
$keywords = @('(BC)', '(TH)', '(XY)')
if (-not (Test-Path $out)) { New-Item -ItemType Directory -Path $out | Out-Null }
# 結果格納用(キーワードごとの配列)
$result = @{}
foreach ($kw in $keywords) { $result[$kw] = New-Object System.Collections.Generic.List[string] }
# 走査は1回
Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
$name = $_.Name
$full = $_.FullName
foreach ($kw in $keywords) {
if ($name -like "*$kw*") {
$result[$kw].Add($full)
}
}
}
# 保存
foreach ($kw in $keywords) {
$result[$kw] | Out-File -FilePath (Join-Path $out "$kw.txt") -Encoding UTF8
}
「同じファイル名が複数キーワードに当てはまる」ケース(例:資料(BC)(TH).xlsx)でも、この方法なら該当する複数のファイルに同時に出力できます。
dir と PowerShell、どちらを選ぶべきか
どちらが正解というより、運用・拡張の見込みで決めるのが現実的です。迷ったら、まず dir で要件を満たし、後からPowerShellに移す、という段階的移行もおすすめです。
| 観点 | dir(コマンドプロンプト/バッチ) | PowerShell |
|---|---|---|
| 導入の手軽さ | 標準搭載、1行で試せる | 標準搭載だがスクリプト管理の発想が必要 |
| 「キーワード別保存」 | for で回せば可能(単純なら強い) | 配列・ハッシュで柔軟に拡張できる |
| 特殊文字への強さ | クォートとエスケープが必要になると急に難しい | 文字列処理が豊富、正規表現の逃がし方も明確 |
| 出力文字コード | 環境のコードページの影響を受けやすい | UTF-8 などを明示でき、取り回しが良い |
| 拡張(除外条件、日付、サイズなど) | 追加するほど読みにくくなる | Where-Object / Sort-Object などで自然に拡張できる |
実運用で失敗しないためのチェックリスト
最後に、同種の「空ファイル」問題を再発させないための、現場寄りチェック項目をまとめます。スクリプトを書き換える前に、ここを押さえるだけでも無駄な試行錯誤が減ります。
- 最初は1キーワード1コマンドで確認し、結果が出ることを確かめてからループ化する
- 対象フォルダーがネットワークドライブ(P:)なら、権限エラーを前提にして
2>nul/-ErrorAction SilentlyContinueを入れる - キーワードに括弧を入れる目的(過剰一致回避)を忘れず、検索パターンに必ず括弧を含める
- PowerShell で
-matchを使うなら、[regex]::Escape() で安全なパターンにしてから判定する - 出力ファイルを他ツールで扱うなら、文字コード(UTF-8等)を明示する
- キーワードが増える運用なら、keywords.txt で外部化して編集ミスを減らす
よくある質問
括弧つきキーワードを外すと何が困る?
たとえば TH だけで検索すると、WITH や THESIS など、意図しないファイル名まで大量にヒットします。命名規則として「識別子を括弧で囲む(例:(TH))」運用なら、検索側も括弧を含めることでノイズを強力に減らせます。
dir の結果を「ファイル名だけ(パスなし)」にしたい
/b を付けても /s を付けるとフルパスになります。フォルダー階層を無視してファイル名だけにしたいなら、PowerShell で Select-Object -ExpandProperty Name を使う方が確実です。dirでやる場合は、for /r で回して %%~nxF を出すなど、別の書き方が必要になります。
検索結果をCSVで欲しい
dir はテキスト出力が得意です。CSVが必要なら、PowerShell で Select-Object して Export-Csv するのがシンプルです(フルパス、サイズ、更新日時なども列として持たせられます)。

コメント