LoginSignup
20
6

More than 1 year has passed since last update.

はじめに

現在でも、Windows環境を用いたレガシーなプロジェクトでは根強く生き続けているWindowsバッチを使用したアプリ。
他のアプリと比べても癖が強く、理解できないよう謎の仕様も多々あります。

その謎の仕様で最たるものが、今回紹介する「遅延環境変数」(遅延環境変数の展開)と呼ばれるものです。

今回はその謎の仕様である「遅延環境変数」を簡単におさらい、整理します。

まずはこれを見てください

normal.bat
@echo off
set num=0
if %num% == 0 (
   set /a num = num+1
   echo num1=%num%
)
echo num2=%num%

このコードでは、numが「0」の場合に「1」を加算し、加算した結果をif内とif外でecho(コンソール表示)するシンプルなコードです。

得られる結果として、

num1=1
num2=1

と想像つきますが、実際は

num1=0
num2=1

となってしまいます。

numに1を加算しているにもかかわらず、1つ目のechoは「0」と表示されてしまいます。
何故でしょうか。

何故こうなるのか

そもそもWindowsバッチはプログラム言語ではなく、あくまでもコマンドラインの集合体であるため、1行単位で変数を展開・実行する仕組みになっています。

if文については if ( ~ ) までを1行としてみなして展開されます。
その為、展開された時点ではまだnumは「0」の状態であり、if内のechoのnumの値は「0」として処理されます。1

遅延環境変数とは

例えば、if内のechoを「num=1として処理したい」(加算された状態)場合、どうするのか。

ここで「遅延環境変数」(遅延環境変数の展開)といったものが必要となります。

delay.bat
@echo off
setlocal enabledelayedexpansion

set num=0
if %num% == 0 (
   set /a num = num+1
   echo num1=!num!
)
echo num2=%num%

endlocal

遅延環境変数を使用するには、上記のように対象範囲をsetlocal enabledelayedexpansionendlocal で囲み、遅延環境変数を用いたい箇所の変数をパーセント(%)ではなく、エクスクラメーション(!)で囲みます。2
(囲んだ範囲が遅延環境変数のスコープ)

遅延環境変数を使用することにより、変数の値を置き換えるタイミングを変えることができます。
つまり、行が展開されるタイミングではなく、実際にそのコードが実行されるべきタイミング(今回の場合、if内のecho)で値を置き換えることができます

このコードで実行すると

num1=1
num2=1

と、想定通りの結果が得られます。

for文でも同様

先述の例ではif文を取り上げましたが、for文もfor( ~ )を1行とみなすため、同様の事象が発生します。

例)下記フォルダ構成で、拡張子「txt」のファイル名をfor文でリスト表示したい場合

D:.
└─work
    │  AA.txt
    │  BB.txt
normal_for.bat
@echo off
set name=DEFAULT

for  %%a in (D:\work\*.txt) do (
    set name=%%a
    echo %name%
)

結果は

DEFAULT
DEFAULT

となってしまいますが、

ここで遅延環境変数を用いて実行すると、

delay_for.bat
@echo off
setlocal enabledelayedexpansion

set name=DEFAULT

for  %%a in (D:\work\*.txt) do (
    set name=%%a
    echo !name!
)

endlocal

結果は

D:\work\AA.txt
D:\work\BB.txt

と、期待通りの結果が得られます。

補足

補足①:setlocal以外での遅延環境変数の有効化

遅延環境変数についてはsetlocal enabledelayedexpansionを用いなくても、以下の方法(「1.」「2.」)でも可能です。
ただし、以下の方法は環境への依存が高くなる為、setlocal~を用いてアプリ内に明示的に宣言しておいた方が移行など発生した場合の保守性は高いと考えます。

方法 内容
1.cmdコマンドのオプションで指定 cmdコマンドのオプション「/V:ON」を実行し、遅延環境変数を有効化する。
2.レジストリの値を変更 レジストリファイル内のエントリ「DelayedExpansion」の値を「1:有効」にする。

この方法を使うと、setlocal enabledelayedexpansionがなくてもエクスクラメーション(!)で囲まれた変数を自動的に遅延環境変数とみなします。
また「1.」「2.」を共に設定している場合、「1.」の設定の方が優先されます。

補足②:遅延環境変数の値の中にエクスクラメーションが含まれる場合の注意

変数名ではなく、遅延環境変数の値の中にエクスクラメーション(!)が含まれる文字を取扱う場合(パスワードやファイル名等)、遅延環境変数が正しく認識されず誤作動を起こす可能性があります。
その場合はsetlocalendlocalの範囲を変えて遅延環境変数のスコープを変更するか、あるいは遅延環境変数のコード箇所を関数化して外出しにする等の工夫が必要となります。

まとめ

 モノ作りをしてるとここはこう動くべきとかこうあるべきとか偏った先入観が働きがちですが、その言語の個性や特性、意味を把握した上で取り組むことも大事なのかと思い、このようなテーマを挙げさせていただきました。

 参考になったかは分かりませんが、今後の皆さんの作業に役立てていただけると幸いです。


  1. if外のechoについては、if文が展開された後(加算が行われた状態)であるため、「num」には「1」がセットされる。 

  2. パーセント(%)で変数名を囲むのは値を参照する場合。set文で値を代入もしくは演算する場合は変数にパーセント(%)は不要。 

20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6