2008年11月12日水曜日

遅延環境変数

引数に実行日時を設定しEXEを何回か連続で実行するというバッチを作ったのですが、実行日時が更新されないという現象に悩まされました。

hoge.bat
@echo off
for /l %%i in (1,1,5) do (
hoge.exe %date% %time:~0,8%
)
pause

実行イメージ
hoge.exe 2008/11/12 23:45:01
hoge.exe 2008/11/12 23:45:01
hoge.exe 2008/11/12 23:45:01
hoge.exe 2008/11/12 23:45:01
hoge.exe 2008/11/12 23:45:01


原因は、環境変数(%date%、%time%)の展開を各コマンド実行のタイミングではなく、バッチファイル読み取り時に行っているためみたいです。
そして、この現象を環境変数の即時展開と呼ぶそうです。


解決策として下記2点の修正が必要だそうです。
まず、遅延環境変数の展開という機能を 使うために以下の宣言を行います。
setlocal enabledelayedexpansion

次に、この展開機能を用いる環境変数を「%」ではなく、「!」で囲みますので、完成形は次のようになります。
@echo off
setlocal enabledelayedexpansion
for /l %%i in (1,1,5) do (
hoge.exe !date! !time:~0,8!
)
pause


まぁ、実行日時が更新されないぐらいなら可愛いですけど、下記のif文すらきちんと動いてくれないというのは困ったものですよね。
@echo off
set VAR=before
if "%VAR%" == "before" (
set VAR=after;
if "%VAR%" == "after" echo SUCCESS!!
)
pause

この仕様はどうにかならないのでしょうか・・・。

8 件のコメント:

匿名 さんのコメント...

@echo off
setlocal enabledelayedexpansion
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" echo SUCCESS!!
)
endlocal
pause

で、いけるんじゃない?

Unknown さんのコメント...

>俊介さん
それでいけるんですけど、そもそも遅延環境変数を知らないと、こんな簡単なif文も作れないというのはどうかなと思いまして…。
普段私がバッチを作り慣れていないせいかもしれませんけど。
この仕様、知ってました?

匿名 さんのコメント...

いや、知らなかったけど、
コーディングってそんなもんじゃない?

日付計算で8日,9日がエラーになるのと
感覚としては同じだなー。

まぁ、調べて、理解して、活用していく
過程が、コーディングの面白さってことで。

Unknown さんのコメント...

>俊介さん
8日、9日がエラーになるのは知らないです・・・。

自分達が作っているシステムさえも完璧とは程遠いのに、ましてやそれが言語やコンパイラとなると想像つかない領域ですからね。

きっと、それを前提にコーディングにとりかかるべきなんでしょうね。
まぁ、何だかんだ言って、私もコーディングは好きなので。

匿名 さんのコメント...

【Example1】
@echo off
rem 今日
set yy=%date:~0,4%
set mm=%date:~5,2%
set dd=%date:~8,2%
echo 今日 ::: %yy%年%mm%月%dd%日
pause

rem 昨日
set /a dd=%dd%-1
set dd=00%dd%
set dd=%dd:~-2%
echo 昨日 ::: %yy%年%mm%月%dd%日
pause

【Example2】
@echo off
rem 今日
set yy=%date:~0,4%
set mm=%date:~5,2%
set dd=%date:~8,2%
echo 今日 ::: %yy%年%mm%月%dd%日
pause

rem 昨日
if %dd%==09 set dd=9& rem avoid 09
if %dd%==08 set dd=8& rem avoid 08
set /a dd=%dd%-1
set dd=00%dd%
set dd=%dd:~-2%
echo 昨日 ::: %yy%年%mm%月%dd%日
pause

【Example1】が正しいように見えるけど、
【Example2】が正しい。
8日,9日で試してみれば分かるよ。

まぁ、こういうのを見つけるのも、
コーディングの面白さのひとつだよね。

※即席で作ったので、1日の対応は
 していませんが、ご了承ください。

Unknown さんのコメント...

>俊介さん
サンプルまで作っていただきありがとうございます。

if %dd%==09 set dd=9& rem avoid 09
if %dd%==08 set dd=8& rem avoid 08

やっぱり上記のコードの必要性が分からないです・・・。
12月8日と9日に試してみます。
ちなみに、remの前に存在する"&"は特別の意味があるんでんすか?


set /a dd=%dd%-1
set dd=00%dd%
set dd=%dd:~-2%

ここのコードの書き方は勉強になりました。
引き算による桁落ちのフォローをしているんですね。

匿名 さんのコメント...

前提として、、
set /a の数値は10進数です。
でも、接頭辞として、
0xを付けると「16進数」、0を付けると「8進数」
と認識します。

上記だけで理解できれば素晴らしい。

そう、「08」「09」は、8進数として有効な
数値ではないためにエラーとなるのです。

これが答えです。


具体的に、8日の場合
if %dd%==08 set dd=8& rem avoid 08

①if %dd%==08 set dd=8
 これは問題ないでしょう。上記の通り。
②rem avoid 08
 これも問題ないでしょう。コメント。
③&
 演算子。①と②をつないでいるだけ。
 ②の位置にコメントを入れたいだけです。

>12月8日と9日に試してみます。
 PCの日付を変更すればいいのでは。。

Unknown さんのコメント...

>俊介さん
再度、丁寧なコメントをありがとうございます。

>set /a の数値は10進数です。
>でも、接頭辞として、
>0xを付けると「16進数」、
>0を付けると「8進数」と認識します。
なるほど。そういうことでしたか。
確かにset /?で説明がされていますね。

『コマンド スクリプト外でコマンド ラインから SET /A を実行すると、・・・
数値は 10 進数ですが、接頭辞として 0xを付けると 16 進数、0 を付けると 8 進数になります。従って、0x12 は 18、あるいは 022 と同じです。8 進表記を使う場合は、注意してください。08 や09 は、8 と 9 が有効な 8 進数ではないため、有効な数値ではありません。』

今回の書き込みで思いかけず、色々な勉強をさせてもらうことができました。
これもブログを書き続ける1つのメリットですね。
これからもたまには技術ネタを書かせてもらいますので、よろしくお願いします。