Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
99
Help us understand the problem. What is going on with this article?
@plcherrim

バッチファイル界の魔境『遅延環境変数』に挑む(おまけもあるよ)

More than 3 years have passed since last update.

 今回は、forと双璧を為すバッチファイル界の魔境、遅延環境変数に挑みたいと思います。

1.遅延環境変数とは?

 さて、大分前に投稿した.bat(バッチファイル)のforコマンド解説。の中で、次のようなことを述べました。(見なくても問題ないです)

 特に遅延環境変数については、for文を使う上でほぼ確実に理解する必要が出てくると思われます。

 その理由を説明するには、バッチファイルでの「変数を読み込むタイミング」について知っておく必要があります。

変数の読み込みタイミング

 次のコードをご覧下さい。

例1.bat
@set num=1
@if %num% == 1 (
    set /a num+=1
    echo %num%
)
@pause

 ちなみに、このページ内にあるコード表示は、そのままコピー&ペーストでバッチファイルとして動くようになっています。 実行しても、ええんやで。

 さて、話を戻して…。上のコード、実行結果はどうなると思いますか?
 まず最初はnumが1なので、if文は真になりますね。ここまでは特に大したことはありませんが…。
 if文の最初で、numに1を加算しています。つまり、numの値は2になっているはずです。次の行で出力しているので2と表示されるはずなのですが…?

 このコード、出力結果は1になります。

 この現象こそが、変数を読み込むタイミングが顕著に影響を及ぼす例です。
 通常変数は%記号で囲みますが、この%で囲った変数は、その変数がある行に『入った瞬間』にすべて展開されます。

 そして、カッコで囲った複数のコマンドは、その行数に関わらず、1行とみなして変数が展開されます。

 つまりこのコード、if文に入った瞬間に変数が全て展開されてしまうので、echo時に加算が反映されていないのです。

 加算自体は行われていますので、カッコを抜けた後に再度echoしなおせば、きちんと2と表示されます。


 というわけで、変数の読み込みタイミングは行に入った瞬間であること、カッコは1まとまりと認識されることが分かりました。

 では、遅延環境変数とは何か?についての答えを出したいと思います。

 遅延環境変数とは、変数を読み込むタイミングを遅らせる変数である!

2.遅延環境変数とは?

 遅延環境変数を使うには、変数を囲む%記号を!記号に変えるだけ。
 こうすると、その変数を読み込むタイミングが、『行に入った瞬間』から『その変数を使うコマンドにたどり着いた瞬間』になります。

 試しに、先ほどのコードのecho行を遅延環境変数に変えてみましょう。

例1.bat
@setlocal enabledelayedexpansion
@set num=1
@if %num% == 1 (
    set /a num+=1
    echo !num!
)
@pause

 これで、きちんと2がechoされます。ですが、変な行が増えていますね?

 setlocal enabledelayedexpansionと書かないと、遅延環境変数が使えませんので、使う場合は何も考えずに文頭に書いてください。

3.いつ使う?~①かっこ内で~

 遅延環境変数は、基本的にかっこの中で使うことが多いです。その理由は先述した通りですが、ではかっこを使うのってどんな時でしょうか。

 それは、if文とfor文です。もちろんほかのコマンドで使うこともありますが、群を抜いて最多はこの2つでしょう。それぞれどんな使い方をするのかについては、別に解説ページを設けていますので作者マイページからご覧いただければ幸いです。

4.いつ使う?~②変数を二重に読み込む~

 突然ですが。あなたはバッチファイルで数列の項を計算しています。n項めの数字を、number[n]という名前の変数にそれぞれ保存しました。

 さて、計算が終わり、いざまとめて出力する段になりました。forは難しいので、ラベルを使ってn回のループを行い、数列の各項の数字を出力したいです。

 ……といった場合。
 変数の名前はnumber[%n%]ですので、表示しようとしたらecho %number[%n%]%…?

 これでは動きませんね。なぜなら、%number[%n%]%という変数は、%number[%%]%という分割のされ方をしてしまうためです。ではどうすればいいか?

 !number[%n%]!としてみましょう。さながら数式の小かっこと中かっこのような役割を果たします。行に入った瞬間に%%コマンドに入った瞬間に!!が読み込まれるわけですね。

4-おまけ.変数を2重に読み込むその他の方法(2016/5/29追記)

 実は変数を2重に読み込む方法というのはほかにも存在します。それらをご紹介します。

方法1.call set~2回解釈させる~

 echo %number[%n%]%では動かないという話をしました。遅延を使わずに二重展開するにはどうすればよいでしょうか?
 答えは、echo文を2回解釈させるです。具体的には、call echo %%number[%n%]%%としましょう。

 callは、以降のコマンドを実行する、という効果を持ちます。(それ以外もたくさんありますが。)
 そしてコマンドを読み込むと、%は変数のしるしとして、%%%ひとつとして解釈されます。つまり、call echo %%number[%n%]%%echo %number[nの中身]%を実行しろ、という指示になり正常に実行できるというわけですね。

 ちなみに、!!としても同様の効果を得ることはできません。できるのは%%だけですので気を付けましょう。

 また、この使い方をすると、外側の変数(ここでは%num[nの中身]%のほう)は遅延読み込み扱いになります。callが終わった後にechoコマンドに辿り着くので、『その変数を使うコマンドにたどり着いた瞬間』という遅延の条件を強制的に満たさせるわけですね。
 ちなみに、内側(ここでは%n%のほう)は、↑の条件を満たさないため自動で遅延にはなりません。カッコ内でcallを用いた2重読み込みをしたい場合は、call echo %%number[!n!]%%とするとうまくいきます。曲芸みたいなことをしていますが、考えながら読めば理解は難しくないと思います。

方法2.set /a~計算式中の変数は常に遅延展開される~

 もうひとつ、変数の中身が数字の場合に限り可能な方法を挙げます。
それは、set /a a=number[%n%]とすることです。余分に変数が一つ必要になりますが、これで変数aの中に二重に展開された数が代入されます。数字を代入したい場合(カッコの中などで結構多いです)はこちらのほうがスマートだと思います。

 どういう原理かというと、まずset /a中では変数を%や!でくくる必要がありません。式中の変数は、すべてset /aコマンド開始時に自動で読み込まれます。
 コマンド開始時に……ということは、遅延展開ということですね。文中の%%は行に入った瞬間、!!もset /aより先に読み込まれますので、『くくった変数が読み込まれる→くくってない変数が遅延で読み込まれる』という流れを達成したことになります。!!も先に読み込んでくれますので、カッコ中ではset /a a=number[!n!]として問題ありません。

5.まとめ

 遅延環境変数は、①かっこ内で変数を使うとき、②変数を2重に読み込むときに使用します。とはいえ最初のうちは慣れないことと思いますので、様々なところでこまめにechoして挙動を理解しましょう。

 最後まで読んでいただき、有難うございました。
 誤り・感想・要望などありましたら、是非コメントにお願いいたします。

99
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
plcherrim
バッチファイルが好き。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
99
Help us understand the problem. What is going on with this article?