定例処理用バッチファイルでログを日時付きファイル名で出力する

問題編

Windowsのタスクスケジューラなどで定例処理を行う時、ログないしレポートの類を毎回別の名前のファイルに出力したい場合がある。

この名前の付け方には、次の2パターンが考えられる。

  1. ファイル名に連番を付ける。
  2. ファイル名に処理を実行した日時を付ける。

定例処理の場合、大抵ほぼ一定の間隔(例えば1日1回とか、毎週金曜日の深夜とか)で実行することが大半だ。後でログを見るときに、日時をキーとして検索することも多い。なので個人的には2番目の方法を採用することが多い。

で、タスクスケジューラを使って自動実行させる時にログファイル名も自動的に生成させたい。できればバッチファイルの中で。

解決編

バッチファイルを使う場合、ファイル名に日時を付ける時に使えそうなコマンドはdateとtimeだ。少なくともWindows XP以降ではオプション /t 付きで実行すれば日付や時刻を表示するだけで新しい日付/時刻の入力は求められない。もしくは環境変数%DATE%や%TIME%を使用してもよいだろう。timeと異なり%TIME%では秒単位(小数2桁)も出力される。

シェルスクリプトとは異なり、バッチファイルではコマンドの実行結果の出力を環境変数に設定するにはちょっとばかり面倒な処理が必要だ。環境変数%DATE%や%TIME%を使う方が楽だろう。

ただし、これらの方法で取得した日付や時刻の文字列にはファイル名として使用できない文字(`/' と `:')が含まれているので少し工夫が必要だ。

@echo off
setlocal

:: 標準では YYYY/MM/DD
echo %DATE%

:: YYYY-MM-DD に変換
echo %DATE:/=-%

:: YYYYMMDD に変換
echo %DATE:/=%

:: 例えば report_YYYYMMDD.log
set OUTFILE=report_%DATE:/=%.log
echo %OUTFILE%> "%~dp0%OUTFILE%"

endlocal

上の例では%DATE%の出力文字中の `/' を `-' に変換するか削除してファイル名として使用できるようにしている。

環境変数%TIME%を使う場合は事情が異なる。%TIME%のフォーマットは「HH:MM:SS.mm」のような感じだ。このうち HH の部分のみゼロパディングされずに半角スペースでパディングされる。そのため午前0時〜午前9時の間も正常に処理できるように気を付けなくてはならない。

その上で `:' を削除するか別の文字に変換する必要があったり、小数点の部分だけ区切り文字が `.' だったり、場合によっては小数以下が不要だったりと、%DATE%を使用する場合よりも加工する部分が多くなる傾向にある。

この場合は複数の環境変数を駆使して頑張るよりもコマンドforを使用して一括加工した方が良いだろう。次の例ではコマンドechoで日付と時刻を出力し、その結果をコマンドforでトークン分割し、それぞれのトークンを組み合わせて出力ファイル名を生成してい
る。

@echo off
setlocal

:: 出力ファイル名:report_YYYYMMDD_hhmmss_ss.log
for /f "usebackq tokens=1-7 delims=/:." %%A in (`echo %DATE%/%TIME: =0%`) do (
    set OUTFILE=report_%%A%%B%%C_%%D%%E%%F_%%G.log
)

:: 定時処理代わりに出力ファイル名をファイルに書き出す
echo %OUTFILE%> "%~dp0%OUTFILE%"

endlocal

echoで%TIME%を出力する時に HH をゼロパディングしている。またコマンドforを使用してはいるものの、処理は1回しか実行されない。

取り合えずこんな感じで日時付きのファイル名を生成できる。社内サーバ等ではこれで十分だろう。

問題点、課題

この方法で取得できる日付と時刻はローカルタイムだ。UTCを使用したい場合はWSHを使うことになる。少なくともJScriptならDateオブジェクトでUTCの時刻情報を取得できる。

処理全体をバッチファイルからWSHに書き直すのも面倒なので、JScriptUTCの時刻を出力するスクリプトを作成し、バッチファイル内でコマンドforで結果を取得するようにすれば良いだろう。

あとこの方法は決してユニークな(重複しない)ファイル名を生成している訳ではないことにも注意。