バッチでファイル名をキーワード抽出してテキスト保存する方法|括弧(BC)も正しく検索

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"

オプションの意味は次のとおりです。

指定意味今回うれしいポイント
/bbare形式(余計なヘッダーやサイズ表示を出さない)そのまま一覧として保存しやすい
/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 するのがシンプルです(フルパス、サイズ、更新日時なども列として持たせられます)。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次