よくハマるバッチスクリプト(遅延環境変数)

いつもチャチャっと作る事を要求されるバッチスクリプトですが、
初めの頃はよくハマったことを記述します。

遅延環境変数ってご存知でしょうか。

ForやIFの中で使用している変数は、
値が代入された瞬間にはまだ代入しきれていないのです。
以下の例をみてください。

——————————————————–
例1、遅延環境変数を考慮してない
——————————————————–

@echo off
SET ERRORLEVEL=
SET syori_yyyymmdd=%date:~0,4%%date:~5,2%%date:~8,2%
SET import_error_path=C:\AAAA*%syori_yyyymmdd%*.csv
SET filename=
SET bat_err_file=
SET allerr=

    REM エラーディレクトリを見てファイルがある場合
    if exist %import_error_path% (

        for /f "usebackq" %%i in (`dir /b /od %import_error_path%`) do (   ・・・①
            call :CHECK_IMPORT_ERROR_RESULT %%i %1                         ・・・②
            if %errorlevel% GEQ 1 (                                        ・・・③
                exit /b 1                                                  ・・・④
            )                                                              ・・・⑤
        )                                                                  ・・・⑥
    )
endlocal
exit /b 0

:CHECK_IMPORT_ERROR_RESULT                                                 ・・・(A)

    SET filename=%1
    SET bat_err_file=%filename:~0,19%
    SET CHKFILE=%2\BACK\%bat_err_file%*.csv

    if not exist %CHKFILE% (
        exit /b 1
    ) else (
        exit /b 0
    )
exit /b 0

:set_errorlevel
exit /b %1

①のFORの中で(A)のサブルーチンを呼び、
その戻り値を③で判断しています。
(A)のサブルーチンの結果が③で判断できているように見えますが、
このままでは③のerrorlevelはいつまでも0なので、エラーの判断が出来ません。

これは、①~⑥のFor文はスクリプト上1行と判断されているようで、
FORの中で変数にいくら値を代入しても、その値はForに入る前の状態のまま
判断をするようなのです。

これではやりたい事ができないので、
以下のように修正をします。

——————————————————–
例2、遅延環境変数を考慮する
——————————————————–

@echo off
SET ERRORLEVEL=
SET syori_yyyymmdd=%date:~0,4%%date:~5,2%%date:~8,2%
SET import_error_path=C:\AAAA*%syori_yyyymmdd%*.csv
SET filename=
SET bat_err_file=
SET allerr=

    setlocal enabledelayedexpansion                                        ・・・⑦
    REM エラーディレクトリを見てファイルがある場合
    if exist %import_error_path% (

        for /f "usebackq" %%i in (`dir /b /od %import_error_path%`) do (   ・・・①
            call :CHECK_IMPORT_ERROR_RESULT %%i %1                         ・・・②
            if !errorlevel! GEQ 1 (                                        ・・・③
                exit /b 1                                                  ・・・④
            )                                                              ・・・⑤
        )                                                                  ・・・⑥
    )
endlocal
exit /b 0

:CHECK_IMPORT_ERROR_RESULT                                                  ・・・・(A)

    SET filename=%1
    SET bat_err_file=%filename:~0,19%
    SET CHKFILE=%2\BACK\%bat_err_file%*.csv

    if not exist %CHKFILE% (
        exit /b 1
    ) else (
        exit /b 0
    )
exit /b 0

:set_errorlevel
exit /b %1

⑦の宣言が重要で、この宣言をすると、[!]を使用できるようになります。
[%]ではなく、[!]で変数を囲むと、リアルタイムに代入された値を参照する事ができます。
よって、④のerrorlevelはサブルーチン(A)の結果を正しく判定できるという訳なのです。

純粋にロジックだけ見ると、例1のソースも正しく見えるのですが、
このあたりは知っているか知らないかの差で生産性が変わってくる
部分なのかなと思いますので、ぜひご活用ください。

コラム

次の記事

AIと感情の話