Uncategorized

遅延環境変数

thatisgraffiti

業務でbatファイルを作成するでしょうか。
forを使用する際に、あれ値がおかしい?と思ったら、ぶつかる問題があります。
遅延環境変数です。

遅延環境変数て何?て最初に思いました。
遅延環境変数は、Windowsバッチで「変数が思ったとおりに変わらない」問題を解決するための仕組みで、ループやIF内での変数更新にはほぼ必須の機能です。


遅延環境変数とは何か

通常の環境変数は、バッチファイルの行が「読み取られたタイミング」で展開されます(即時展開)。
一方、遅延環境変数は、行が「実行されるタイミング」で展開されるようにしたものです。

  • 即時展開: %VAR% の形で参照し、構文解析時に値が決まる。
  • 遅延展開: !VAR! の形で参照し、コマンド実行時に値が決まる。

この「いつ展開されるか」の違いが、IF や FOR の中で変数が変わらないように見える原因になります。


有効化方法と基本構文

遅延環境変数を使うには、2ステップが必要です。

1.遅延展開を有効化する

    @echo off
    setlocal EnableDelayedExpansion

    または小文字で enabledelayedexpansion でも動作します。


      2.変数参照を %VAR% から !VAR! に変える

      set COUNT=0
      set /a COUNT+=1
      echo COUNT=!COUNT! 

      これで、実行時点の最新の値が参照されます。

        システム全体で常に遅延展開を有効にする方法として、cmd /V:ON オプションやレジストリ DelayedExpansion を使うやり方もありますが、バッチ単位で setlocal EnableDelayedExpansion を使う方が安全です。


        なぜ必要になるのか(典型的な落とし穴)

        IF 文の中で値が変わらない例

        @echo off
        set VAR=A
        if %VAR%==A (
            set VAR=B
            echo %VAR%
        )

        この場合、多くの人は B が出ると思いますが、実際には A が出ることがあります。
        理由は、IF ブロック全体が「読むとき」に %VAR% が展開されてしまい、その後の set VAR=B が反映されないからです。

        遅延展開にするとこうなります。

        @echo off
        setlocal EnableDelayedExpansion
        set VAR=A
        if %VAR%==A (
            set VAR=B
            echo !VAR!
        )
        endlocal

        この場合は B が出力されます。


        FOR 文でカウンタが増えない例

        @echo off
        set COUNT=0
        for %%F in (*.txt) do (
            set /a COUNT+=1
            echo COUNT=%COUNT%
        )

        ここでも %COUNT% は FOR 文評価時に固定されるため、ずっと同じ値が表示されます。

        遅延展開を使うと、繰り返し毎に増加した値が見えるようになります。

        @echo off
        setlocal EnableDelayedExpansion
        set COUNT=0
        for %%F in (*.txt) do (
            set /a COUNT+=1
            echo COUNT=!COUNT!
        )
        endlocal

        実行すると、COUNT=1 COUNT=2 … のように期待通りに増えていきます。


        よく使うパターンと実例

        1. ループでのファイル名取得

        複数ファイルを処理しながら「今のファイル名」を変数に入れる典型パターンです。

        @echo off
        setlocal EnableDelayedExpansion
        
        set NAME=DEFAULT
        for %%A in (D:\work\*.txt) do (
            set NAME=%%A
            echo !NAME!
        )
        
        endlocal

        !NAME! を使うことで、各ループで更新されたファイルパスが正しく表示されます。


        2. 条件付きでインクリメント

        @echo off
        setlocal EnableDelayedExpansion
        
        set NUM=0
        if %NUM%==0 (
            set /a NUM+=1
            echo num1=!NUM!
            echo num2=%NUM%
        )
        
        endlocal

        この例では遅延展開 !NUM! と即時展開 %NUM% の違いを同時に確認できます。​
        num1 は更新後の 1、num2 は展開タイミングの都合で同じく 1 になるような例として説明されています。


        注意点・ハマりどころ

        遅延環境変数にはいくつか特有の注意点があります。

        • スコープ(影響範囲)
          setlocal EnableDelayedExpansion 以降にだけ有効で、endlocal で元に戻ります。
          必要な範囲だけ setlocal~endlocal で囲むと、ほかへの影響を減らせます。
        • !(感嘆符)の扱い
          遅延展開を有効にすると、! は変数展開の記号になるため、文字列中の ! が消えたり誤展開されることがあります。
          その場合は ^ でエスケープします。
        @echo off
        setlocal EnableDelayedExpansion
        
        set ESCAPED="escaped^!"
        set UNESCAPED="unescaped!"
        
        echo !ESCAPED!
        echo !UNESCAPED!
        
        endlocal 
        • ^! と書いた部分は、実際の出力では ! にな​ります。
        • パフォーマンスと可読性
          何でもかんでも遅延展開にすると、スクリプトが読みづらくなります。
          「ループや IF ブロックの中で値を書き換えて参照したいときだけ」使う、というルールにしておくと整理しやすいです。

        実務で使うときの指針

        • レガシーな Windows バッチの保守で、「IF や FOR の中で値が変わらない」現象に遭遇したら、まず遅延環境変数を疑う。
        • ループや複雑な条件分岐を含むバッチでは、冒頭に setlocal EnableDelayedExpansion を書き、ブロック内の参照は !VAR! に統一する。
        • システム全体設定(cmd /V:ON やレジストリ変更)は影響範囲が広くトラブルの元になるため、基本はバッチ内で完結させる。​

        例えば、ログファイルを走査して条件に一致した行数をカウントするバッチは、FOR ループ中でカウンタ変数を !COUNT! で参照すれば、期待どおりの値でログが取れるようになります。

        ABOUT ME
        記事URLをコピーしました