バッチファイルでファイルの存在を確認して処理を切り替える——これは実務バッチの最も基本的なパターンです。設定ファイルがなければ初期作成する、コピー先が既にあれば上書きをスキップする、ロックファイルが存在すれば二重起動を防ぐ、といった制御がif exist ひとつで実現できます。
本記事では if exist の基本から、AND/OR 複合条件・空ファイル判定・更新日時チェック・ファイル内容検索・ループとの組み合わせまで実践コードで体系的に解説します。
- if exist / if not exist で即時終了する安全な前提チェックパターン
- ワイルドカードでフォルダ内に特定拡張子のファイルが存在するか確認する
- ディレクトリ確認と自動 mkdir を組み合わせる定番パターン
- 複数ファイルがすべて揃っているか(AND)、どれか1つでもあるか(OR)を判定する
- ファイルサイズが0バイト(空ファイル)かどうかを判定する
- ファイルの更新日時を使って「今日作成されたファイルか」を判定する
- ファイルの内容(キーワード)を確認して分岐する
- for ループと組み合わせて複数ファイルを一括存在確認する
- ロックファイル・設定ファイル初期化などの実践パターン
ファイル存在チェックのパターン一覧
| パターン | 構文 | 用途 |
|---|---|---|
| 存在する場合に処理 | if exist “path” ( … ) | ファイルがあるときだけ実行 |
| 存在しない場合に処理 | if not exist “path” ( … ) | ファイルがないときだけ実行 |
| 存在しなければ即終了 | if not exist “path” exit /b 1 | 前提ファイルの必須チェック |
| ワイルドカード | if exist “dir\*.log” ( … ) | 特定拡張子のファイルが1つでもあるか |
| ディレクトリ確認 | if exist “dir\” ( … ) | 末尾に \ を付けるとフォルダ判定 |
| AND複合条件 | if exist “a” if exist “b” ( … ) | 複数ファイルすべて存在する |
| OR複合条件 | フラグ変数 + ループで実装 | いずれか1つでも存在する |
| 空ファイル判定 | for %%F + if %%~zF equ 0 | サイズが0バイト |
| 更新日時チェック | for /f + wmic または forfiles | 今日/指定日付より新しいか |
| 内容確認 | findstr との組み合わせ | 特定キーワードを含むか |
NOT EXIST・ELSE・NUL を使った基本文法の詳細はIF EXISTでファイル・フォルダの存在確認完全ガイドを参照してください。本記事は実践的な条件分岐パターンに重点を置いて解説します。
if exist / if not exist の安全パターン
最も重要なのは「前提ファイルが存在しなければ処理を即中断する」パターンです。処理の冒頭で前提条件を確認し、不足があれば早期に終了するのが安全なバッチ設計の基本です。
@echo off
setlocal
set "INPUT=C:\data\input.csv"
set "CONFIG=C:\config\app.ini"
REM 前提ファイルが存在しなければエラーコードを変えて即終了
if not exist "%INPUT%" (
echo [ERROR] 入力ファイルが見つかりません: %INPUT%
exit /b 1
)
if not exist "%CONFIG%" (
echo [ERROR] 設定ファイルが見つかりません: %CONFIG%
exit /b 2
)
echo [OK] ファイル確認完了。処理を開始します
REM 処理本体...
endlocal
@echo off
setlocal
set "CONFIG=C:\config\app.ini"
REM 親ディレクトリがなければ先に作成する
if not exist "C:\config\" mkdir "C:\config"
if not exist "%CONFIG%" (
echo 設定ファイルが存在しないため初期ファイルを作成します
(
echo [General]
echo LogLevel=INFO
echo MaxRetry=3
echo Timeout=30
) > "%CONFIG%"
echo [OK] 設定ファイルを作成しました: %CONFIG%
) else (
echo [OK] 設定ファイルを確認しました: %CONFIG%
)
endlocal
ファイルごとに終了コードを変える(1, 2, 3…)ことで、呼び出し元バッチや監視ツールがどのファイルが不足していたかを特定できます。エラー処理の設計についてはエラーで処理を中断する方法完全ガイドも参照してください。
ディレクトリの存在確認と自動作成
ログやバックアップの出力先ディレクトリが存在しない場合、コピーや書き込み処理がエラーになります。if not exist と mkdir を組み合わせて自動作成するのが定番パターンです。
@echo off
setlocal
set "LOGDIR=C:\logs\batch"
set "BACKUPDIR=D:\backup\daily"
REM ディレクトリ確認には末尾の \ を付けるのが確実
if not exist "%LOGDIR%\" (
mkdir "%LOGDIR%"
echo [作成] ログディレクトリを作成しました: %LOGDIR%
)
if not exist "%BACKUPDIR%\" (
mkdir "%BACKUPDIR%"
echo [作成] バックアップ先を作成しました: %BACKUPDIR%
)
echo 処理を開始します
endlocal
@echo off
REM cmd の mkdir は中間ディレクトリも含めて一度に作成できる
REM (例: C:\work\2026\03\reports が存在しなくても一発で作成可能)
set "OUTDIR=C:\work\2026\03\reports"
if not exist "%OUTDIR%\" (
mkdir "%OUTDIR%" 2>nul
if %ERRORLEVEL% equ 0 (
echo [OK] ディレクトリ作成: %OUTDIR%
) else (
echo [NG] ディレクトリ作成失敗(権限またはパス不正): %OUTDIR%
exit /b 1
)
)
Windows の
mkdir コマンドに /s オプションはありません。ただし mkdir "C:\a\b\c" のように中間ディレクトリを含む深いパスを指定しても、一度に全階層を作成できます(Linux の mkdir -p 相当の動作)。失敗した場合は 2>nul でエラーメッセージを抑制しつつ%ERRORLEVEL% で成否を判断するのが安全です。ワイルドカードで特定拡張子ファイルの存在を一括確認
フォルダ内に特定拡張子(*.csv、*.log)のファイルが1件でもあるかを確認するときにワイルドカードが便利です。ただし if exist "dir\*.csv" は「1件でも存在すれば真」であり、件数の取得はできません。
@echo off
setlocal
set "INBOX=C:\data\inbox"
REM *.csv が1件でも存在するか確認(存在しなければスキップ)
if not exist "%INBOX%\*.csv" (
echo [SKIP] 処理対象の CSV ファイルがありません
exit /b 0
)
echo [OK] CSV ファイルを検出しました。処理を開始します
REM for ループで実際のファイルを1件ずつ処理
for %%F in ("%INBOX%\*.csv") do (
echo 処理中: %%~nxF
REM 処理本体...
)
endlocal
@echo off
setlocal
set "DIR=C:\import"
set "FOUND=0"
REM 複数の拡張子をそれぞれチェック(OR条件の簡易版)
if exist "%DIR%\*.csv" set "FOUND=1"
if exist "%DIR%\*.tsv" set "FOUND=1"
if exist "%DIR%\*.txt" set "FOUND=1"
if "%FOUND%"=="1" (
echo [OK] インポート対象ファイルを検出
) else (
echo [SKIP] インポート対象ファイルなし
exit /b 0
)
endlocal
AND / OR 複合条件でのファイル存在チェック
「複数のファイルがすべて存在する(AND)」か「どれか1つでも存在する(OR)」かを判定するパターンです。
@echo off
setlocal
set "FILE_A=C:\data\master.csv"
set "FILE_B=C:\data\transaction.csv"
set "FILE_C=C:\config\rules.ini"
REM 不足ファイルをすべて表示してから終了するパターン
set "MISSING=0"
if not exist "%FILE_A%" ( echo [不足] %FILE_A% & set "MISSING=1" )
if not exist "%FILE_B%" ( echo [不足] %FILE_B% & set "MISSING=1" )
if not exist "%FILE_C%" ( echo [不足] %FILE_C% & set "MISSING=1" )
if "%MISSING%"=="1" (
echo [ERROR] 必要なファイルが不足しています
exit /b 1
)
echo [OK] 全ファイル確認完了。処理を開始します
endlocal
@echo off
setlocal
set "FILE_A=C:\data\master.csv"
set "FILE_B=C:\data\transaction.csv"
REM if をネストすることで AND 条件を表現する
if exist "%FILE_A%" (
if exist "%FILE_B%" (
echo [OK] 全ファイルあり
goto :PROCESS
)
)
echo [ERROR] ファイルが不足しています
exit /b 1
:PROCESS
echo 処理を開始します
endlocal
@echo off
setlocal
set "FOUND=0"
set "CONFIG_PATH="
REM 候補パスのうち最初に見つかったものを使用する(OR条件)
for %%F in (
"C:\app\config.ini"
"C:\ProgramData\app\config.ini"
) do (
if "!FOUND!"=="0" (
if exist %%~F (
set "FOUND=1"
set "CONFIG_PATH=%%~F"
)
)
)
if "%FOUND%"=="0" (
echo [ERROR] いずれの場所にも設定ファイルが見つかりません
exit /b 1
)
echo 使用する設定ファイル: %CONFIG_PATH%
endlocal
AND/OR を使った複合条件の詳細パターンは複数の条件をANDで結合する完全ガイドも参照してください。
ファイルサイズ 0 バイト(空ファイル)の判定
ログファイルの出力が空でないか、CSVにデータ行があるかを確認するにはfor %%F in ("path") do ... %%~zF でサイズを取得して判定します。ループ内でカウンタ変数を使う場合は setlocal enabledelayedexpansion が必要です。
@echo off
setlocal
set "FILE=C:\data\output.csv"
if not exist "%FILE%" (
echo [ERROR] ファイルが存在しません
exit /b 1
)
REM %%~zF でファイルサイズ(バイト数)を取得
for %%F in ("%FILE%") do set "SIZE=%%~zF"
if %SIZE% equ 0 (
echo [SKIP] ファイルが空です(0バイト): %FILE%
exit /b 0
) else (
echo [OK] ファイルサイズ: %SIZE% バイト
echo 処理を開始します
)
endlocal
@echo off
setlocal enabledelayedexpansion
set "TARGETDIR=C:\data"
set "LOGFILE=C:\logs\empty_files.txt"
set "COUNT=0"
REM ループ内でカウンタを更新するため enabledelayedexpansion が必要
for %%F in ("%TARGETDIR%\*") do (
if %%~zF equ 0 (
echo %%~nxF >> "%LOGFILE%"
set /a "COUNT+=1"
)
)
echo 空ファイル数: %COUNT%
if %COUNT% gtr 0 echo 詳細: %LOGFILE%
endlocal
for ループ内で set /a "COUNT+=1" を使う場合、setlocal enabledelayedexpansion がなくても加算自体は動作しますが、ループ内で !COUNT! を参照して分岐したい場合は遅延展開が必須です。習慣として enabledelayedexpansion を付けておくと安全です。ファイルサイズによる多段階判定はファイルサイズを条件分岐に活用する完全ガイドも参照してください。ファイルの更新日時による条件分岐
「今日作成・更新されたファイルだけ処理する」「指定日数より古いファイルをスキップする」といった日付ベースの条件分岐を実装する方法です。
@echo off
setlocal
set "FILE=C:\data\report.csv"
if not exist "%FILE%" (
echo [ERROR] ファイルが存在しません
exit /b 1
)
REM forfiles /d 0 = 今日以降に更新されたファイルを対象にする
forfiles /p "C:\data" /m "report.csv" /d 0 >nul 2>&1
if %ERRORLEVEL% equ 0 (
echo [OK] report.csv は今日更新されています
) else (
echo [SKIP] report.csv は今日更新されていません
exit /b 0
)
echo 処理を継続します
endlocal
@echo off
setlocal
set "SRCDIR=C:\data\inbox"
REM /d 0 = 今日以降、/d -1 = 昨日以降、/d -7 = 7日以内
REM 対象ファイルが0件の場合 forfiles はエラーコードを返す
forfiles /p "%SRCDIR%" /m "*.csv" /d 0 /c "cmd /c echo @path" >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo [SKIP] 本日更新の CSV ファイルがありません
exit /b 0
)
echo [OK] 本日更新の CSV ファイルを処理します
REM 本日更新分のみを処理
forfiles /p "%SRCDIR%" /m "*.csv" /d 0 /c "cmd /c echo 処理: @file"
endlocal
@echo off
setlocal
set "FILE=C:\data\data.csv"
REM 今日の日付を YYYYMMDD 形式で取得
for /f "tokens=2 delims==" %%D in (
'wmic os get LocalDateTime /value'
) do set "TODAY=%%D"
set "TODAY=%TODAY:~0,8%"
REM ファイルの最終更新日時を YYYYMMDD で取得
for /f "tokens=2 delims==" %%D in (
'wmic datafile where "name='C:\\data\\data.csv'" get LastModified /value'
) do set "FILEDATE=%%D"
set "FILEDATE=%FILEDATE:~0,8%"
if "%FILEDATE%"=="%TODAY%" (
echo [OK] ファイルは今日 (%TODAY%) 更新されています
) else (
echo [SKIP] ファイルの更新日 (%FILEDATE%) が今日 (%TODAY%) と異なります
)
endlocal
forfiles /d 0 は「今日(当日0時以降)」、/d -1 は「昨日以降」、/d -7 は「7日以内」を意味します。正の数(/d +1)は「明日以降」を指定できます。指定日数より古いファイルを削除する方法は期間以前に更新されたファイルを自動削除する完全ガイドも参照してください。ファイルの内容(キーワード)を確認して条件分岐する
findstr で特定キーワードを検索し、見つかれば処理を続行・見つからなければスキップ、という「内容ベースの条件分岐」を実装できます。
@echo off
setlocal
set "LOGFILE=C:\logs\batch.log"
if not exist "%LOGFILE%" (
echo [SKIP] ログファイルが存在しません
exit /b 0
)
REM ログに ERROR が含まれるか確認(/i = 大文字小文字を無視)
findstr /i /c:"ERROR" "%LOGFILE%" >nul 2>&1
if %ERRORLEVEL% equ 0 (
echo [ALERT] ログにエラーを検出しました
call :NOTIFY_ERROR
) else (
echo [OK] エラーなし
)
exit /b 0
:NOTIFY_ERROR
echo %DATE% %TIME% エラー検知 >> "C:\logs\alert.log"
goto :eof
@echo off
setlocal
set "CONFIG=C:\config\app.ini"
if not exist "%CONFIG%" (
echo [WARN] 設定ファイルなし。デフォルト設定で起動します
set "LOGLEVEL=INFO"
goto :START
)
REM DEBUG モードが有効かどうかを設定ファイルで確認
findstr /i /c:"Debug=true" "%CONFIG%" >nul 2>&1
if %ERRORLEVEL% equ 0 (
echo [DEBUG] デバッグモードで実行します
set "LOGLEVEL=DEBUG"
) else (
echo [INFO] 本番モードで実行します
set "LOGLEVEL=INFO"
)
:START
echo ログレベル: %LOGLEVEL%
endlocal
for ループとの組み合わせで複数ファイルを一括チェックする
処理対象ファイルのリストを事前に確認してから実行することで、途中でエラーになるリスクを下げられます。不足ファイルをすべて列挙してからまとめてエラー終了するパターンも実装できます。
@echo off
setlocal
set "MISSING=0"
REM 必須ファイルのリストを一括チェック
for %%F in (
"C:\data\master.csv"
"C:\data\trans.csv"
"C:\config\app.ini"
"C:\keys\cert.pem"
) do (
if not exist %%~F (
echo [不足] %%~F
set /a "MISSING+=1"
) else (
echo [OK] %%~F
)
)
if %MISSING% gtr 0 (
echo.
echo [ERROR] %MISSING% 件のファイルが不足しています。処理を中断します
exit /b 1
)
echo.
echo [OK] 全ファイルの確認完了。処理を開始します
endlocal
@echo off
setlocal
set "SRCDIR=C:\data\inbox"
set "DSTDIR=C:\data\processed"
if not exist "%DSTDIR%\" mkdir "%DSTDIR%"
for %%F in ("%SRCDIR%\*.csv") do (
REM コピー先に同名ファイルが存在しなければコピー
if not exist "%DSTDIR%\%%~nxF" (
copy "%%F" "%DSTDIR%\%%~nxF" >nul
echo [コピー] %%~nxF
) else (
echo [スキップ] %%~nxF は処理済みです
)
)
endlocal
ファイル一覧の取得方法についてはフォルダ内ファイル一覧を取得する完全ガイドも参照してください。
実践パターン:存在チェックを組み込んだ実用スクリプト
@echo off
setlocal
set "LOCKFILE=C:\flags\batch_running.lock"
set "LOCK_TIMEOUT_MIN=60"
if not exist "C:\flags\" mkdir "C:\flags"
if exist "%LOCKFILE%" (
REM ロックファイルが古い(異常終了で残った)場合は強制解除
forfiles /p "C:\flags" /m "batch_running.lock" /d -0 >nul 2>&1
if %ERRORLEVEL% equ 0 (
echo [SKIP] 別のプロセスが実行中です。終了します
exit /b 0
) else (
echo [WARN] 古いロックファイルを検出。強制解除して続行します
del "%LOCKFILE%" >nul 2>&1
)
)
REM ロックファイルを作成
echo %DATE% %TIME% > "%LOCKFILE%"
call :MAIN_PROCESS
set RC=%ERRORLEVEL%
del "%LOCKFILE%" >nul 2>&1
exit /b %RC%
:MAIN_PROCESS
echo 処理を実行中...
timeout /t 3 /nobreak >nul
echo 処理完了
goto :eof
@echo off
setlocal
set "SRC=C:\data"
for /f "tokens=1-3 delims=/" %%A in ("%DATE%") do set "DT=%%A%%B%%C"
set "DEST=D:\backup\%DT%"
set "LOGFILE=C:\logs\backup_%DT%.log"
REM バックアップ元の存在確認
if not exist "%SRC%\" (
echo [ERROR] バックアップ元が存在しません: %SRC%
exit /b 1
)
REM バックアップ先を作成
if not exist "%DEST%\" mkdir "%DEST%"
REM バックアップ実行
robocopy "%SRC%" "%DEST%" /e /log:"%LOGFILE%" /np
set RC=%ERRORLEVEL%
REM バックアップ後: ログファイルが生成されているか確認
if not exist "%LOGFILE%" (
echo [WARN] ログファイルが生成されませんでした
) else (
for %%F in ("%LOGFILE%") do set "LOGSIZE=%%~zF"
echo [OK] バックアップ完了: ログサイズ=%LOGSIZE% バイト
)
REM robocopy は 0〜7 が成功、8 以上がエラー
if %RC% geq 8 (
echo [ERROR] バックアップ失敗: RC=%RC%
exit /b %RC%
)
endlocal
ファイルコピーの詳細はファイルをコピーする完全ガイド、ファイル削除の安全なパターンはファイルを削除する完全ガイドも参照してください。
よくある落とし穴とトラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
| ファイルが存在するのに if exist が偽になる | 変数展開でスペースを含むパスが分断される | "%FILE%" のようにダブルクォートで囲む |
| ワイルドカードが空フォルダでも真になることがある | . や .. などの特殊エントリにマッチしている | for ループでフラグ変数を使う確実な方法に切り替える |
| mkdir でエラー「サブディレクトリまたはファイルは既に存在します」 | 既存ディレクトリに対して mkdir を実行した | if not exist "dir\" で事前確認するか 2>nul でエラーを抑制する |
| ループ内で set /a のカウンタが増えない | 遅延展開なしで !COUNT! を参照している |
setlocal enabledelayedexpansion を追加して !変数名! で参照する |
| forfiles で「ファイルが見つかりません」エラー | 指定した /m パターンに一致するファイルが0件 | if %ERRORLEVEL% neq 0 でファイルなしとして処理を分岐する |
| wmic で取得した日付が空になる | wmic が存在しない環境(Win11 24H2 以降で廃止) | PowerShell Get-Date -Format yyyyMMdd に切り替える |
まとめ
- if not exist + exit /b: 前提ファイルの必須チェックは処理冒頭で。ファイルごとに異なる終了コードで不足箇所を特定できる
- 末尾に \ を付けてディレクトリ判定:
if not exist "dir\"+mkdirでフォルダ自動作成が定番パターン。mkdir は中間パスも一度に作成できる - ワイルドカード:
*.csvで「1件でも存在するか」を一発確認。件数を数えたい場合は for ループを使う - AND条件: 不足一覧を全件表示してから exit するパターンが実用的
- OR条件: フラグ変数をループで設定してループ後に判定
- サイズ0判定:
%%~zF equ 0で空ファイルを検出。ループ内カウンタには enabledelayedexpansion が必要 - 更新日時チェック:
forfiles /d 0で今日更新されたファイルのみを対象にできる - findstr との組み合わせ: ファイルの内容(キーワードの有無)で分岐できる
- ロックファイルパターン: 二重起動防止に存在チェックを活用。タイムスタンプで異常終了後の残骸も検出できる
関連記事: IF EXISTでファイル・フォルダの存在確認完全ガイド / ファイルの存在を監視するバッチ完全ガイド / 複数の条件をANDで結合する完全ガイド
よくある質問(FAQ)
if exist でフォルダを確認するとき、末尾の \ は必ず必要ですか?if exist "C:\logs\" とすると logs という名前のディレクトリのみに一致します。末尾 \ なしではファイルとフォルダの両方にマッチするため、厳密な判定が必要な場合は必ず付けてください。if exist "C:\data\*.csv" としましたが、フォルダが空でも真になることがあります。.(カレントディレクトリ)や ..(親ディレクトリ)にマッチしている可能性があります。for %%F in ("C:\data\*.csv") do set "FOUND=1" のようにループでフラグを立てる方法の方が確実です。ループ実行前に set "FOUND=0" でリセットすることを忘れないようにしてください。if exist が偽になることがあります。set "FILE=%~dp0data file.csv" のようにスペースを含むパスを%FILE% のままダブルクォートなしで if exist に渡すとパスが途中で切断されます。必ず if exist "%FILE%" とダブルクォートで囲んでください。また setlocal enabledelayedexpansion 環境では !FILE! で参照が必要な場合もあります。COUNT を使っていますが、ループ終了後も 0 のままです。set /a "COUNT+=1" は実行されていますが、ループブロック内で %COUNT% を参照すると括弧ブロック全体がパース時に展開されるため、ループ開始時の値(0)が表示されます。setlocal enabledelayedexpansion を追加し、ループ内では !COUNT! で参照してください。ループ終了後の参照(if %COUNT% gtr 0)では通常の %変数% で問題ありません。forfiles /d -0 でロックファイルの作成が今日かどうかを確認し、古ければ「前回の異常終了で残ったファイル」とみなして強制削除するパターンが有効です。より正確には forfiles /d 0 で「当日更新」かどうかを判定し、当日でなければ古いロックファイルとして削除してから処理を継続します。上記の lock_guard.bat サンプルに実装例を示しています。

