スクリプト ファイル中に単にコマンドを並べるだけなら、作成も簡単であるし、それなりに有用ではあるが、これでは単なるテープレコーダーのようなもので、場合によっては使えないことがある。例えば前述のbup.cmdでは、もしbackupというフォルダがなかった場合、最初の文にあるcopyコマンドはfile1.txtをbackupという名前のファイルにコピーしてしまうし、次の文のcopyコマンドでは、file2.txtを同じくbackupというファイルに上書きしてコピーしてしまう。また、file1.txtやfile2.txtが存在しない場合には、エラーとなる。
ユーザーが作業するような場合には、事前にdirコマンドでフォルダやファイルの有無を調べてから作業するだろう。同じようにコンピュータにもこうした実行の前提となる条件を調べさせ、条件を満たしたときだけ実行を行わせる必要がある。このために使われるのが条件判断という手段である。条件判断は、プログラミングでは非常に基本となる動作だ。
cmd.exeには、条件判断のために“if”というスクリプト用の組み込みコマンドがある(スクリプト中でなく、例えばforコマンドなどと組み合わせてコマンド ライン上で直接使うこともあるが、一般的にはこれはスクリプト中で使われることが多い)。このコマンドは、
if 条件 実行文1 [ else 実行文2 ]
という形で利用する。“[ else 実行文2 ]”は、このelse部分が省略可能であることを示している。「条件」部分には、いろいろなものが指定できるが、ここでは、ファイルの存在をテストする“exist”だけ説明しておこう(ifコマンドについては今後改めて解説するが、詳しくは“help if”コマンドを参照してほしい)。ファイルが存在しているかどうかをテストするには、
if exist c:\file1.txt echo "Exist file"
のように“if”の後ろに空白を1つ以上入れて、“exist”キーワードとファイル名を指定する。もし指定したファイルが存在すれば、実行文として指定したechoコマンド(echoの引数として指定された文字列を、そのまま表示するコマンド)が実行され、文字列(この場合は“Exist file”という文字列)が表示されるはずである。また、指定したファイルがない場合に何か処理をしたければ、
if not exist c:\file1.txt 〜
というふうに、ifとexistの間に“not”を入れる。notは、指定した条件を反転するためのキーワードである。“if not exist 〜”ならば、ファイルがないときに条件が成立する。
さて多くの条件判断では、ある条件を満たしたときと、そうでないときに処理の内容を変えるということが行われる。ifを使って、指定したファイルがある場合とそうでない場合に表示される文字列を切り替えるには、
if exist c:\file1.txt ( echo Exist ) else echo Not Exist
と指定する。もし、“c:\file1.txt”というファイルがあれば、直後のカッコ内にある文“echo Exist”が実行され、そうでなければ、“echo Not Exist”が実行される。カッコ “ ( ) ” は、コマンドをグループ化するためのもので、これを使わないとelse以下も最初のechoの引数として解釈されてしまい(“echo Exist else echo Not Exist”となる)、ファイルが存在したときに、“Exist else echo Not Exist”という文字列が表示されてしまう。なおカッコでくくった場合には、改行で区切って(つまり複数の行に分けることにより)複数のコマンドを記述することもできる。
従来のMS-DOSでは(Windows 9xに組み込まれているcommand.comも含む)、if文にelseキーワードがなかったため、このような場合には、goto文を使って、プログラム中に2つのブロックを作って実行を行わせていた。具体的には、
(1) if not exist c:\file1.txt goto notexist
(2) echo Exist
(3) goto skip1
(4) :notexist
(5) echo Not Exist
(6) :skip1
(7) ……後続する文……
とするしかなかったのである。この文を解説しておくと、gotoコマンドは、指定したラベルへの移動を行うために使用するコマンドである。例えば、
goto skip
echo Not execute Line 1
echo Not execute Line 2
:skip
echo execute
となっていた場合、最初のgoto文で、4行目の“skip”というラベルへ制御が移り、そこから実行される(“:skip”は、ラベルと呼ばれ、gotoの移動先を示すために使われる。ラベル名には任意の英数字が指定できるが、ラベルの先頭には“:”を付ける。“:”の直後にスペースを置くことはできない)。このラベル文は、移動先を示すだけで何も実行しない文なので、実際には、5行目の“echo execute”が実行されることになる。この例では、2行目と3行目のecho文は、まったく実行されない。
さて、先のgoto文とif文の組合せの例だが、(1)の文で、“c:\file1.txt”というファイルがなかったときに“notexist”というラベルを持つ(4)の行へ制御が移る。そうでない場合(つまりc:\file1.txtが存在するとき)には、if文は条件を満たさないので、その直後の行へ制御が移り、(2)、(3)と実行される。(3)には、goto文があるので制御は(6)にある“skip1”というラベルへ移ることになる。その後(7)が実行されることになる。
ファイルが存在しなかった場合、(4)へ制御が移ったあと、(5)を実行し、(6)はそのまま通り抜け、やはり(7)が実行される。
このような記述の方法は少し古めかしく感じるかもしれないが、プログラミングではよく出てくる構造であるので、活用していただきたい。
あるディレクトリが存在するかどうかのチェック
Windows 2000付属のヘルプ ファイル(「Windows 2000コマンド リファレンス」内の「ifコマンド」の解説にある関連項目から、「Ifの例」を選択する)によると、最初のスクリプトの例で、“c:\backup”というディレクトリが存在するかどうかを知りたければ、ディレクトリ名の最後に“\nul”を付けて、
if exist c:\backup\nul 〜
というふうにif文の条件部を指定すればよい、と記述されている。existはファイルやディレクトリが存在しているかどうかを調査するためのコマンドであり、指定された名前のファイルでもディレクトリでもあれば、条件を満たしてしまう。そこで、どこにでも存在するとされているnulという名前のデバイス ファイル名(nulデバイスとは、データを書き込んでも無視され、読み出そうとしてもすぐにEnd of Fileになってしまう特殊なデバイス)をディレクトリ名の後ろに付加することで、backupがディレクトリであることをチェックするわけである。もしbackupがファイルならば、c:\backup\nulというパスは存在しないため、条件は成立しないのでディレクトリでないということが分かるはずである。
しかしこの方法を実際に使ってみると、パス名の中にスペース(空白)が含まれている場合には、正しく意図したとおりに動作しないようである。スペースを含むパスをスクリプト内で記述するには、これをダブルクオートでくくる必要があるのだが、こうするとうまく動作しないのである。例えば、
if exist "c:\Program Files\nul" 〜
if exist "c:\Program Files"\nul 〜
のいずれの書き方でも条件が不成立となってしまう。このよう場合も含めて正しく動くようにするには、dirコマンドとfindコマンドを組み合わせて、
dir "c:\Program Files" 2>nul ^
| find "<DIR> .." >NUL
if not errorlevel 1 echo folder exist
※1行目の最後にある「^」は、行が継続していることを表す。ここではコラム幅の都合上、2行に分割しているが、実際には1行にまとめてもよい。
などとするしかないようである。これは、dirコマンドのパラメータにディレクトリを指定したときの出力である、
C:\>dir test
ドライブ C のボリューム ラベルはありません。
ボリューム シリアル番号は A001-C875 です
C:\test のディレクトリ
00/07/17 02:47 <DIR> .
00/07/17 02:47 <DIR> ..
:
:
という出力テキスト中に、上位ディレクトリ(「..」)の項目が必ず含まれていることを利用して、その有無をfindコマンドで検出し、その結果をerrorlevelコマンドで調べるという操作を行っている。この方法は、前述のnulデバイスを使うものと比べるとスマートではないが(というか、かなり情けない方法である)、Windows 9xやMS-DOSといった環境でも利用できるというメリットがある(ただしdirコマンドの出力フォーマットが異なるので、変更が必要)。
もう1つこの問題を避ける方法として、例えば、“c:\Program Files”の8.3形式の短い名前が“c:\PROGRA~1”であることを利用する方法もある(短い名前は“dir /x”コマンドなどで確認できる)。これならば空白が含まれていないので、名前の最後に“\nul”を追加する方法で判定することができるが(“if exist c:\PROGRA~1\nul 〜”とする)、対応する短い名前が何であるかを常にきちんと把握しておかないと動作しないという問題がある(8.3文字以下で、かつ特殊な記号などが含まれていなければ、短い名前が作られないので、常にこのようなディレクトリ名を使うようにする、という方法もある)。
ディレクトリの有無を判断してファイルをコピーするスクリプト
さてそれでは、最初のスクリプトを書き直して、ディレクトリが存在する場合にはファイル コピーをして、そうでない場合にはエラー メッセージを出力するスクリプトを作ってみよう。ただしディレクトリ名に空白や特殊記号が含まれている場合の処理はいろいろと面倒なので省略し(必要ならば各自で対応していただきたい)、末尾に“\nul”を付加する方法で判定するものとする。
if exist c:\backup\nul (
copy c:\file1.txt c:\backup
copy c:\file2.txt c:\backup
) else (
echo Error c:\backup Folder not exist
)
ここでは、まずc:\backupというディレクトリがあるかどうかを調べて、存在する場合にはファイルのコピー処理を行うが、そうでない場合はエラー メッセージをecho文で出力している。このとき注意するのは、elseキーワードは、閉じカッコと同じ行に記述しなければならないということである。if文には、else部分がない書式もあるので、
if exist c:\backup\nul (
copy c:\file1.txt c:\backup
copy c:\file2.txt c:\backup
)
else (
echo Error c:\backup Folder not exist
)
というようにしてしまうと、4行目の閉じカッコのところでif文は終わったものとして扱われ、5行目のelse部がエラーになってしまう。なおelseを使うと文が長くなるが、スクリプトが読みにくくなることを避けるためにも、なるべくカッコを使うようにしたほうがよい。スクリプトでは、もともと実行効率はそれほどよくないため、不要なカッコをなくしてもそれほど実行速度が向上するわけではない。ならば読みやすさを重視した構造にするほうが、あとのメンテナンスを考えると有利になる。goto文を多用するのもプログラムが読みにくくなる原因となるので注意したい。
Copyright© Digital Advantage Corp. All Rights Reserved.