バッチファイルで関数化してみる
バッチファイルでのログ出力とエラーハンドリングの実装例
本記事では、Windows環境で動作するバッチファイル main.bat
と、そのログ出力ライブラリ loglib.bat
を用いたサンプルコードを解析しながら、ログ出力やエラーハンドリングの実装方法について解説します。
外部ファイルを参照できるようにして、コード管理や使いまわしなども考慮してみました。
サンプルコードの概要
今回説明するサンプルコードは以下の2つのファイルで構成されています。
なお、Command002
では、意図的にエラーが発生するように書いてあります。
-
main.bat
メインの処理を記述したバッチファイル。ログの出力やエラーハンドリング、各種コマンドの呼び出しなどを行っています。 -
loglib.bat
ログ出力専用のライブラリ。指定されたメッセージをコンソールとログファイルの両方に出力します。
main.bat の詳細解説
以下に main.bat
の内容とその解説を示します。
@echo off
setlocal enabledelayedexpansion
REM メイン処理開始のログ出力
call :LogMessage スクリプト開始
set COUNT=0
for /L %%i in (1,1,3) do (
set /a COUNT+=1
call :Command00!COUNT!
call :LogMessage 3second wait time.
timeout /t 3 >nul
)
REM --- 正常終了 ---
call :LogMessage スクリプト正常終了
goto end
REM --- コマンド1の実行 ---
:Command001
call :LogMessage Command00!COUNT!の実行開始
dir >>nul 2>&1
if errorlevel 1 (
call :LogMessage ERROR: Command00!COUNT!に失敗。エラーコード: %errorlevel%
goto errorHandler
)
call :LogMessage Command00!COUNT!の実行成功
exit /b
:Command002
REM --- コマンド2の実行(意図的にエラーを発生) ---
call :LogMessage Command00!COUNT!の実行開始 (意図的にエラー発生)
cmd /c exit 1 >>nul 2>&1
if errorlevel 1 (
call :LogMessage ERROR: Command00!COUNT!に失敗。エラーコード: %errorlevel%
goto errorHandler
)
call :LogMessage コマンド2の実行成功
exit /b
REM --- コマンド3の実行 ---
:Command003
call :LogMessage Command00!COUNT!の実行開始
dir >>nul 2>&1
if errorlevel 1 (
call :LogMessage ERROR: Command00!COUNT!に失敗。エラーコード: %errorlevel%
goto errorHandler
)
call :LogMessage Command00!COUNT!の実行成功
exit /b
:errorHandler
call :LogMessage スクリプトはエラーにより終了しました
goto end
REM --- ログ出力関数 ---
:LogMessage
REM 引数 %~1 を受け取ってログ出力する(%~1:ダブルクオート除去済みの第1引数)
call loglib.bat %*
exit /b
:end
endlocal
exit /b
主なポイント
-
ログ出力の統一管理
:LogMessage
というラベルを用意し、ログ出力処理を共通化しています。
※内部ではloglib.bat
を呼び出しており、出力先はコンソールとログファイルです。 -
ループ処理とコマンド実行
for %%i in (a b)
により、2回のループを実行。ループ内ではCOUNT
をインクリメントし、動的に:Command001
や:Command002
を呼び出しています(call :Command00!COUNT!
)。 -
エラーチェック
各コマンド実行後にif errorlevel 1
を使用してエラー判定を行い、エラー発生時には:errorHandler
ラベルにジャンプしてエラーログを出力しています。 -
待機処理
timeout /t 3
により、各コマンド実行後に3秒の待機を入れることで、処理の間隔を調整しています。
loglib.bat の詳細解説
次に、ログ出力ライブラリ loglib.bat
の内容を見てみます。
@echo off
setlocal
REM ログフォルダとログファイルの設定
set "LOGDIR=%~dp0logs"
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
set "LOGFILE=%LOGDIR%\execution.log"
REM 引数(すべての引数を連結)をログに出力
echo [%date% %time%] %*
echo [%date% %time%] %* >> "%LOGFILE%"
endlocal
exit /b
主なポイント
-
ログディレクトリの作成
%~dp0
を用いて、実行ファイルと同じディレクトリにlogs
フォルダを作成しています。
※ログディレクトリが存在しない場合は自動的に作成します。 -
ログファイルへの出力
execution.log
というファイルに、日付と時刻を先頭に付加した形でメッセージを追記しています。
※コンソール出力も同時に行っています。
まとめ
今回のサンプルコードでは、以下のような実装テクニックが確認できます。
-
モジュール化
ログ出力処理をloglib.bat
にまとめることで、メインスクリプトからの呼び出しが容易になっています。 -
エラーハンドリング
各コマンド実行後のエラーチェックとエラー発生時の一元管理が実現されています。 -
遅延展開の利用
setlocal enabledelayedexpansion
を利用することで、ループ内で変数の更新を正しく反映させています。
これらのテクニックは、バッチファイルを用いた自動化スクリプト作成時に非常に有用です。特にログ管理とエラー検出の実装は、スクリプトの信頼性向上に大きく寄与します。
補足
setlocal enabledelayedexpansion
は、バッチファイル内で「遅延環境変数展開」を有効にするためのコマンドです。
詳細な説明
-
setlocal
バッチファイルの実行環境を一時的にローカルなものに切り替えます。endlocal
コマンドが実行されるか、バッチファイルの実行が終了するまで、環境変数の変更はその範囲内でのみ有効になります。 -
enabledelayedexpansion
通常、バッチファイルでは変数は行が実行される前に展開(置換)されます。そのため、ループや条件分岐内で変数の値を変更しても、同じコードブロック内ではその変更が反映されないことがあります。
「遅延環境変数展開」を有効にすると、変数を%VAR%
ではなく!VAR!
のように記述することで、変数の値が実際に実行される時点で展開され、更新後の最新の値を取得することができます。
利用例
@echo off
setlocal enabledelayedexpansion
set COUNT=0
for %%i in (a b c) do (
set /a COUNT+=1
echo 現在のカウント: !COUNT!
)
endlocal
上記の例では、!COUNT!
を使うことでループ内で更新された最新の COUNT
の値を表示できます。
この機能は、特にループや条件分岐で変数の値を動的に更新・参照する場合に非常に便利です。
おわりに
本記事では、main.bat
と loglib.bat
のサンプルコードを通して、バッチファイルでのログ出力およびエラーハンドリングの実装例を紹介しました。実際の運用では、さらに複雑な処理やエラーハンドリングが必要となる場合もありますが、本サンプルがその一助となれば幸いです。
皆さんの環境での活用例や改善アイデアなど、ぜひコメントで教えていただけると嬉しいです!
おまけ
loglib.bat
を別の角度から活用した例となります。
batchFiles
というフォルダに、複数のバッチファイルが存在していると仮定して、
それらを一括実行した際に、実行結果を受け取れるという内容になっている。
@echo off
setlocal enabledelayedexpansion
REM --- 実行対象のバッチファイルが格納されているフォルダのパス設定 ---
set "batchFolder=%~dp0batchFiles"
REM メイン処理開始のログ出力
call :LogMessage スクリプト開始
call :Init
call :Main
REM --- 正常終了 ---
call :LogMessage スクリプト正常終了
goto end
REM --- 実行対象のバッチファイルが格納されているフォルダのパス設定 ---
:Init
set "batchFolder=%~dp0batchFiles"
call :LogMessage 初期化します
REM --- フォルダの存在確認 ---
if not exist "%batchFolder%" (
call :LogMessage %batchFolder%が見つかりません。
goto errorHandler
)
call :LogMessage 初期化、完了
exit /b
REM --- フォルダ内のすべてのバッチファイル (*.bat) を順次実行 ---
:Main
call :LogMessage バッチファイル実行
for %%F in ("%batchFolder%\*.bat") do (
call :LogMessage ======================================
call :LogMessage 実行中: %%~nxF
call "%%F"
if errorlevel 1 (
call :LogMessage ERROR: %%~nxF の実行に失敗しました。エラーコード: %errorlevel%
)
call :LogMessage ======================================
timeout /t 3 >nul
)
if errorlevel 1 (
call :LogMessage ERROR: バッチの実行に失敗しました。エラーコード: %errorlevel%
goto errorHandler
)
goto end
exit /b
:errorHandler
call :LogMessage スクリプトはエラーにより終了しました
goto end
REM --- ログ出力関数 ---
:LogMessage
call loglib.bat echo %*
exit /b
:end
endlocal
exit /b