バッチ処理で中間成果物を置く先が決まっていないと、同名衝突や消し忘れが起きやすくなります。安全に運用するためには、OSが用意する一時フォルダを使い、衝突しない名前で作成し、処理の成否に関わらず必ず削除する「後始末の型」を組み込むことが重要です。ここでは cmd.exe 前提で、一時ファイル・一時ディレクトリの生成から確実なクリーンアップまでを実務向けにまとめます。
%TEMP% を起点にする理由と前提設定
一時領域は環境変数 TEMP または TMP が指し示します。ユーザーごとに分離され、アクセス権や高速ストレージの考慮が入っているため、原則としてここを起点にします。バッチ先頭で拡張機能と遅延展開を有効にし、エラー時に即座に終了コードを返す方針にすると安定します。
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "TMPROOT=%TEMP%"
if not exist "%TMPROOT%" (
echo TEMP が見つかりません 1>&2
exit /b 1
)
衝突しない一時ファイルの作り方(純粋 cmd 版)
乱数を利用しつつ存在確認で衝突を避けるのが定石です。作成は NUL からのリダイレクトで空ファイルを生成し、作れなければ再試行します。最大試行回数を決めて無限ループを避けます。
set "BASENAME=tmp-%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%-%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%-!RANDOM!"
set "TMPFILE="
for /l %%I in (1,1,20) do (
set "CAND=%TMPROOT%\%BASENAME%-%%I.tmp"
if not exist "!CAND!" (
break & set "TMPFILE=!CAND!"
)
)
if not defined TMPFILE (
echo 一時ファイルを確保できませんでした 1>&2
exit /b 2
)
type nul > "%TMPFILE%" || (echo 作成に失敗しました 1>&2 & exit /b 3)
echo 一時ファイル: "%TMPFILE%"
より確実に作る方法(PowerShell 連携)
.NET の API を呼び出すと競合に強く、作成と同時に実体が確保されます。GetTempFileName は空のファイルを生成して完全修飾パスを返します。
for /f "usebackq delims=" %%P in (`
powershell -NoProfile -Command "[IO.Path]::GetTempFileName()"
`) do set "TMPFILE=%%P"
if not exist "%TMPFILE%" (
echo PowerShell による一時ファイル作成に失敗しました 1>&2
exit /b 4
)
echo 一時ファイル: "%TMPFILE%"
一時ディレクトリが必要な処理の型
圧縮展開や多数の中間ファイルを扱う処理では一時ディレクトリを確保してから作業します。GetRandomFileName で衝突しづらい名前を生成し、mkdir で実体化します。
for /f "usebackq delims=" %%N in (`
powershell -NoProfile -Command "[IO.Path]::GetRandomFileName()"
`) do set "TMPDIR=%TMPROOT%\%%N"
mkdir "%TMPDIR%" || (echo 一時ディレクトリ作成失敗 1>&2 & exit /b 5)
echo 一時ディレクトリ: "%TMPDIR%"
疑似 try–finally で必ず後始末する
バッチには例外機構がないため、処理の流れに「必ず通る片付け節」を用意します。エラー発生時も成功時も cleanup ラベルへジャンプさせ、存在チェックの上で削除します。ファイルは del、フォルダは rmdir /s /q を用います。
rem ==== メイン処理(例)====
>"%TMPFILE%" echo 中間結果を書き込み中…
if errorlevel 1 goto :cleanup
rem ここで "%TMPDIR%" を作業場所として使用
rem 例)展開・変換・検証など
rem エラーなら任意のコードで終了
rem if エラー条件 goto :cleanup
rem 成功した体で終了コード 0 を維持したまま片付けへ
set "EXITCODE=0"
goto :cleanup
:cleanup
rem 直前のエラーコードを保持していない場合は明示的に設定
if not defined EXITCODE set "EXITCODE=%ERRORLEVEL%"
if defined TMPFILE if exist "%TMPFILE%" del /q "%TMPFILE%"
if defined TMPDIR if exist "%TMPDIR%" rmdir /s /q "%TMPDIR%"
endlocal & exit /b %EXITCODE%
削除できないときの典型原因と対処
プロセスがファイルを開いたままの場合に削除が失敗します。自身のリダイレクト先や別プロセスが握っていないかを確認し、書き込み直後は一拍置くと回避できる場面があります。ウイルス対策ソフトが検査中で一時的にロックすることもあるため、再試行を組み込みます。
set "RETRY=0"
:del_try
if exist "%TMPFILE%" del /q "%TMPFILE%" 2>nul
if exist "%TMPFILE%" (
set /a RETRY+=1
if %RETRY% lss 5 (
timeout /t 1 >nul
goto :del_try
)
echo 一時ファイルの削除に失敗しました 1>&2
)
長いパスや日本語名を含む場合の注意
深い階層で MAX_PATH 制限に触れると削除が失敗します。作業ルートは常に TEMP の直下にし、必要なら \\?\ プレフィックスでパス解決の制限を回避します。日本語名や空白は常に二重引用符で囲み、変数参照も必ず “%VAR%” の形にします。
set "LP=\\?\%TMPDIR%"
if exist "%LP%" rmdir /s /q "%LP%"
テンプレートとしてそのまま使える雛形
一時ファイルと一時ディレクトリの両方を確保し、必ずクリーンアップする最小構成をまとめました。新しいバッチはこの型から始めると安全です。
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "TMPROOT=%TEMP%"
for /f "usebackq delims=" %%P in (`powershell -NoProfile -Command "[IO.Path]::GetTempFileName()"`) do set "TMPFILE=%%P"
for /f "usebackq delims=" %%N in (`powershell -NoProfile -Command "[IO.Path]::GetRandomFileName()"`) do set "TMPDIR=%TMPROOT%\%%N"
mkdir "%TMPDIR%" || (echo TMPDIR 作成失敗 1>&2 & set "EXITCODE=10" & goto :cleanup)
rem --- ここにメイン処理 ---
>"%TMPFILE%" echo sample
copy /y "%TMPFILE%" "%TMPDIR%\copy.txt" >nul || (set "EXITCODE=20" & goto :cleanup)
rem --------------------------
set "EXITCODE=0"
:cleanup
if not defined EXITCODE set "EXITCODE=%ERRORLEVEL%"
if defined TMPFILE if exist "%TMPFILE%" del /q "%TMPFILE%"
if defined TMPDIR if exist "%TMPDIR%" rmdir /s /q "%TMPDIR%"
endlocal & exit /b %EXITCODE%
まとめ
TEMP を起点に一意な名前で一時領域を確保し、処理の最後に必ず通るクリーンアップ節で削除する、という二点を守るだけで、衝突や消し忘れの事故は大幅に減らせます。純粋 cmd でも実現できますが、PowerShell の API を併用すると作成の原子性と可搬性が高まり、長期運用の信頼性が向上します。