cmdで遊んでみた

とある事情により、今月半ばから、今の現場の下の階にある会社に常駐することとなった。
新しい場所では、どのような顔が並んでいるのだろう。
不安半分、興味半分だ。


閑話休題
先日、WindowsXP上でcmdのスクリプトを動かしてたのだが、動きがおかしくてだいぶ困った。
これはその時のメモ書きというか、備忘録。
ちなみに再検証している環境はVista

if

複雑なcmdスクリプトを組みたくなる理由はいくつかあるが、当然ソレは制御構造を表現したい場合が多い。
cmd上でのif文は

if condition somethingToDo

と書くのが基本。複数の文を書きたい場合は、カッコを使う。
具体的にはこうなる

C:\Users\quabbin>if 1 == 1 (
More? echo x
More? echo y
More? )
x
y

C:\Users\quabbin>

ここで、「More? 」は「(」に対するプロンプト。
実際に入力しているのはechoの部分だけ。
当然elseも書ける。

C:\Users\quabbin>if 1 == 2 (
More? echo x
More? ) else (
More? echo y
More? )
y

C:\Users\quabbin>

elseはif側の閉じカッコと同じ行に書かなければいけない。
理由は分からなくもないけど、ちょっと面倒。
else ifは elseの中にifを書く必要がある。

C:\Users\quabbin>if 1 == 2 (
More?   echo x
More? ) else ( if 1 == 2 (
More?     echo y
More?   ) else (
More?     echo z
More?   )
More? )
z

C:\Users\quabbin>

書き方を工夫しないと、見にくい。

for

繰り返しにはforが使える。

C:\Users\quabbin>for %x in (a b c) do echo %x

C:\Users\quabbin>echo a
a

C:\Users\quabbin>echo b
b

C:\Users\quabbin>echo c
c

C:\Users\quabbin>

do の後ろの文が実行される。
実行されるときには、cmdファイルのように実行されたコマンドがエコーされる。
@を付けるとエコーはなくなるので、出力がまとまる。

C:\Users\quabbin>for %x in (a b c) do @echo %x
a
b
c

C:\Users\quabbin>

複数行実行する場合は、doの後ろを()で纏める。

C:\Users\quabbin>for %x in (a b c) do @(
More? echo %x-1
More? echo %x-2
More? )
a-1
a-2
b-1
b-2
c-1
c-2

C:\Users\quabbin>

「(」の前の「@」は、エコーを止めるため。
ちなみに内側のechoに@を入れると、「()」がエコーされる。

C:\Users\quabbin>for %x in (a) do (
More? @echo x
More? )

C:\Users\quabbin>()
x

C:\Users\quabbin>

二つ目のプロンプトは、自動表示されたものだ。

一番納得がいかない部分

さて、DOSのbatファイルからだが、もっと高度な制御はgotoを使うしかない。
本題はここからで、このgotoや「()」の周りの動きが納得いかない。


まずラベル。
for文からの脱出構文が見つからなかったので、gotoで制御したくなり、()内でラベルを使おうとした。
しかし、これがうまくいかない。
どうも()内にラベルを書けないようだ。

C:\Users\quabbin>(
More? echo x
More? :label
More? )
) の使い方が誤っています。

C:\Users\quabbin>

複雑なfor文を作成しようとすると、これが邪魔になる。
ifとelseを組み合わせて何とかするしかないのだろうが、フラグ制御も必要になりそうなのだが、これがまた問題。
()内で変数を変化させようとすると、うまく動かない。

C:\Users\quabbin>for %x in (a b c) do @(
More? set y=%x
More? echo %y%
More? )
%y%
%y%
%y%

C:\Users\quabbin>

この現象は「()」自体の問題らしく、

C:\Users\quabbin>(
More? set z=a
More? echo %z%
More? )
%z%

C:\Users\quabbin>echo %z%
a

C:\Users\quabbin>

値は代入されているが、echoで解釈する場所は、()の外側の値を参照するらしい。
これは%ERRORLEVEL%のような、特殊な変数でも同じ現象が見られる。

C:\Users\quabbin>(
More? cd \\\
More? echo level is %ERRORLEVEL%
More? if ERRORLEVEL 2 echo x
More? if ERRORLEVEL 1 echo y
More? if ERRORLEVEL 0 echo z
More? )
'\\\'
CMD では UNC パスは現在のディレクトリとしてサポートされません。
level is 0
y
z

C:\Users\quabbin>

xyzの出力を見て分かるとおり、ERRORLEVELの値は1になっているにも係わらず、%ERRORLEVEL%は「()」の外側での値、0がそのまま保持される。
ついでにbashの$?と違って、echoはERRORLEVELの値を書き換えない。

まとめ

このように、Windowsのcmdをそのまま使おうとすると、バッドノウハウが面白いように溜まっていく。
複雑なことをさせようとするとすぐこんがらがるので、複雑なことは他の言語でなんとかするべきという設計思想なのだろう。
この状況は、Windows Power Shellなどで解消されるのだろうか?
解消されていて、Windowsの標準構成で使えるようになったら(Windows7の次のバージョンあたりだろうか)、そちらを試してみるのいいのだろうか。

2009/03/08 06:55 追記

Sukoyaさんの助言を受け、変数の展開方法について調べてみたところ、比較的簡単に変数の動きを意図通りにする方法が見つかりました。
というか、そんなところにorz

C:\Users\quabbin>cmd /v:on
Microsoft Windows [Version 6.0.6001]
Copyright (c) 2006 Microsoft Corporation.  All rights reserved.

C:\Users\quabbin>(
More? set x=1
More? echo %x%
More? echo !x!
More? )
%x%
1

C:\Users\quabbin>

キモはcmd /v:onと、表記を「!x!」のように「!」で囲む部分です。