8 注意

最後に、バッチファイルに関する注意を上げていきます。 ただし、以下の記述は私が個人的に調べた上での結果なので、 間違っている可能性もあります。 そのような箇所に気がついたら、是非教えてください。
  1. コマンドプロンプト上で日本語を入力したいときは、 [Alt]-[半角/全角] で行います。 日本語入力を終了するときも [Alt]-[半角/全角] です。
  2. echo コマンドに offon (大文字でも) を指定した場合は、 それは表示する文字列ではなく特別なオプションと見なされるわけですが、 「off」「on」のみを表示させるのに echo を利用する場合は、 7 節の echo の項で説明した空行の表示のやり方のように、 「echo.off」や「echo.on」のように . を echo の直後につけるといいようです。
  3. バッチファイル中で echo と出力リダイレクト (>, >>) で簡単なファイルの書き出しを行うとき (例えば 7 節の echo の項の例参照)、 最後に改行を入れないようにもできるといいのですが、 MS-Windows の echo にはそのようなオプションはないようで21、 とりあえず、MS-Windows に用意されている標準的なコマンドでは、 それはできないようです。

    よって、そういうことを行えるフリーソフトを利用するか、 またはそういうコマンドを自作するとかしないと、とりあえずは無理なようです。

    また、[7] で見つけましたが、 if 文や for 文で使われる ( ) は 複数のコマンドをブロック化するために単独で使うこともできるようです。 これによりバッチファイルから簡単なファイルの書き出しを行う 7 節の echo の例は、以下のように >> なしで書くこともできます。


    (
      echo @echo off
      echo echo これは子バッチです [%%0]
      echo exit /b
    ) > test1.bat
    call test1.bat
    こちらの方が多少見やすいですが、 この方法では ( ) 内部の echo の文中に ( ) を使うことができません (よって 7 節の echo の例の ( ) をここでは [ ] に変えてあります)。
  4. set コマンドによる環境変数の設定で、 = の前後にスペースを入れると、それが変数名や値にも入ってしまいます。 例えば、 のようになってしまいます22
  5. %[変数]% は、その環境変数が定義されていれば もちろんその値になりますが、定義されていないときは、 コマンドプロンプトとバッチファイルではその扱われ方が違うようで、 例えば環境変数 a が定義されていない場合、 %a% は以下のようになります。 なお、%1, %2 等のバッチファイルの引数として使われるものは、 未定義のものは常に空文字列となります。
  6. set コマンドによって設定した環境変数は、バッチファイルの終了後も そのバッチファイルを実行したコマンドプロンプト上に残ります。 よって、そのバッチファイル内だけで使用する一時的な変数は、最後に

    set [変数名]=
    で削除するのを忘れないでください23

    逆に、バッチファイル実行後も環境変数の設定が残るという性質を利用して、 そのコマンドプロンプトの環境変数 (特に PATH 環境変数) を設定するためのバッチファイルを使うこともできます。

  7. コマンドやパラメータは大文字、小文字の区別はされませんが、 ラベル名や環境変数名も大文字、小文字の区別はありません。 ただし、for 文の局所変数だけは、 大文字と小文字が区別されますので注意してください。 よって、バッチファイルで例えば

    for %%a in (1 2 3) do echo [%%a][%%A]
    とすると、

    [1][%A]
    [2][%A]
    [3][%A]
    のように表示されます。
  8. if で条件を書く場合、例えば

    if %1==2 [コマンド]
    のような条件を書くと、バッチファイルのコマンドラインオプションの %1 が与えられなかった場合は

    if ==2 [コマンド]
    と展開されてしまうので、構文エラーとなってしまいます。 よって、if 文の条件の両辺が空文字列となりうる場合は、

    if "%1"=="2" [コマンド]
    のように引用符で囲むといいでしょう。
  9. if 文に else 部分をつけるときは、 4.1 節の 2., 4. のように書く必要があり、 例えば C 言語のように次のように書いてはいけません。 else は、if 文の終わりの行につながっている必要があり、 else で始まる行があるとエラーになります。

    ただし、if の実行部が 1 行で、else の実行部が複数行 (あるいはその逆) であるときは、以下のように書くことはできます。


    if [条件] [コマンド] else (
      [コマンド]
      ..........
    )
  10. if の後ろに else も続けて 1 行で書く場合、 if 部分のコマンドが 1 単語でない場合は、 それを ( ) で囲む必要があります。 そうしないとその後ろの else が認識されません。 例えば、

    if "%a%"=="3" echo 「3 です。」 else echo 「3 以外です。」
    とすると、最初の echo から else も含んで行末までが if 部分の実行コマンドと認識されてしまいますので、

    if "%a%"=="3" (echo 「3 です。」) else echo 「3 以外です。」
    のように書く必要があります。
  11. C 言語の else if

    if ([条件1]) {
      [ブロック1]
    } else if ([条件2]) {
      [ブロック2]
    } else {
      [ブロック3]
    }
    は、バッチファイルでも同様に

    if [条件1] (
      [ブロック1]
    ) else if [条件2] (
      [ブロック2]
    ) else (
      [ブロック3]
    )
    のように書くことができますし、入れ子にして

    if [条件1] (
      [ブロック1]
    ) else (
      if [条件2] (
        [ブロック2]
      ) else (
        [ブロック3]
      )
    )
    のようにすることも可能です。
  12. バッチファイルには C 言語の switch 文はありませんが、 それは if 文と goto 命令を組み合わせれば容易に実現できます。 同様に、C 言語の while 文もありませんが、 これも if 文と goto 命令で代用できます。 ただし、goto の使い過ぎは読みにくいプログラムの元となりますので 注意が必要です24
  13. バッチファイル内では、バッチファイルのコマンドライン引数は %1 から %9 で利用できますが、 %10 と書くとそれは「%1 に文字 0 を追加した文字列」 となってしまいますので、10 番目のオプションを直接は利用できません。

    しかし、オプションは 9 つまでしか認識できないわけではなく、 shift コマンドを利用すればそれより多くのオプションも使うことはできます。 例えば、1 度の shift コマンドにより 10 番目の引数が %9 に、 2 度目の shift コマンドにより 11 番目の引数が %9 になります。

    なお、バッチファイルのオプションの区切りにはスペースだけでなく、 カンマも使えることに注意してください。 逆に、スペースやカンマを含む文字列をオプションとして指定したい場合は、 その部分を二重引用符 " " で囲む必要があります。 ただし、その場合二重引用符付きの文字列となりますが、 例えば %1 からその引用符を取り除いた文字列を使いたい場合は、 %~1 とします。詳しくは、help call 参照。

  14. バッチファイルに与えられたオプションの個数を、 C 言語の argc のように最初に取得したければ、 例えば次のように for 文を利用すればいいでしょう。

    set argc=0
    for %%a in (%0 %*) do set /a argc+=1
    ちなみに上の方法だと、C 言語同様、 argc にはバッチファイル名も入れたコマンドラインオプションの個数が入ります。
  15. %0 はバッチファイル名を表しますが、 これはバッチファイルのヘルプメッセージを作るときに便利です。 例えば、オプションとしてファイル名を 1 つ取るバッチファイルを書いたときに、

    @echo off
    if not "%1"=="" goto run1
    rem ##### 以下はヘルプメッセージ #####
    :help
    echo 使用法: %0 [ファイル名]
    echo   [ファイル名] のファイルの行をランダムに 1 行表示
    exit /b 1
    rem ##### 以下からが main #####
    :run1
    .....
    のように、説明部分のバッチファイル名に当たるところを %0 を使って書けば、 後でバッチファイルの名前を変更してもこの help 部分を変更する必要はなくなります25
  16. copy コマンドは、ワイルドカードを使えば 複数のファイルを一度にコピーすることもできますが、 例えば、カレントディレクトリに
    test1, test2, test3, test4
    のような 4 つのファイルがあって、 このうち test1 と test3 だけをワイルドカードで選ぶことはできませんので、 その 2 つだけを別なディレクトリにコピーする場合は、

    > copy test1 dir
    > copy test3 dir
    のようにするしかありません。 また copy コマンドは、例えば

    > copy test1 .
    のように自分自身のファイルにコピーしようとすると エラーメッセージを出して止まるのですが、 + の形式を使った場合はそうではなく、

    > copy test1+test3 .
    あるいは

    > copy test1+test3
    とすると、test1 と test3 を連結した結果を test1 として上書きします。
  17. MS-DOS の頃、つまりファイル名が

    [名前 (8 文字以下)].[拡張子 (3 文字以下)]
    と制限されていたころは、 すべてのファイル名にマッチさせるワイルドカードは *.* と書いてしましたが、 今は * だけですべてのファイル名にマッチします。 ちなみに MS-DOS の頃は、* は 「拡張子がないすべてのファイル」を意味していました。

    なお、今でも *.* はすべてのファイル名にマッチするので、 現在のコマンドプロンプトに関する本でも すべてのファイル名にマッチするパターンとして *.* を紹介しているものは少なくないようです。

    逆に、MS-DOS の頃の「拡張子がないすべてのファイル」を意味する * に相当するワイルドカードパターンは、現在は存在しません。

  18. コマンドの出力を環境変数に代入するには、次のような方法があります。

    > date /t > tmpf
    > set /p s= < tmpf
    こうすると date /t の出力が tmpf というファイルに保存され、 それが set /p にリダイレクトされて環境変数 s に代入されます。

    しかし、これを中間ファイルなしにパイプで 以下のようにやってもうまくいきません。


    > date /t | set /p s=
    理由は正確にはわかりませんが、set が cmd.exe の内部コマンドなので、 パイプ渡しができないのではないかと想像しています。

    なお、日付はこのようにしなくても、 動的な環境変数 %date% で参照できます (3 節参照)。

  19. set /a の右辺では通常の環境変数の値は % なしで参照できますが、 3 節の最後に紹介した %random% 等の動的な環境変数は、set /a の右辺でも % が必要です。 例えば、

    > set /a a=random
    > set /a b=%random%
    とすると、a には 0 が、b には乱数値が入ります。 前者は、多分「静的」な環境変数 random を探してそれがないために 0 が代入されているのではないかと思います。
  20. バッチファイルのオプションの %[n] がファイルやディレクトリ名を指している場合、 そこからドライブ名、パス部分、拡張子部分などを取りだすことができます。 例えば %3 がファイル名 (例えば D:\hoge というディレクトリにある "file1.txt") である場合、 これらは複数組み合わせて %~dx3 (上の例では D:.txt) のように使用することもできますし、 for 文の局所変数でも同じ仕組みが利用できます。 詳しくは、help call, help for 参照。
  21. help set を見るとわかりますが、 環境変数から部分文字列を取得したり、文字列の簡単な置換も行えます。 [$m$] は、先頭の文字列を「0 番目」と数えます。 [$m$] として負の値を指定すると、 文字列の先頭からではなく後ろから数えます (一番後ろの文字が $(-1)$ 番目)。 また、[$n$] として負の値を指定すると、 最後の [$n$] 文字を削除したもの、となります。 例えば、環境変数 var が「1234567890123」という文字列の場合、 となります。詳しくは、help set 参照。
  22. for 文のブロック内で局所変数以外の環境変数を使用する場合、 それはデフォルトでは「for 文が実行される前」 に展開されてしまうことになっているので、 期待通りの結果が得られない場合があります。 例えばコマンドプロンプト上で

    > set j=0
    > for %a in (1 2 3) do set /a j+=%a
    > echo %j%
    とすると、期待通り 1+2+3 の結果の 6 が表示されるのですが、

    > set s=
    > for %a in (竹 の 茂治) do set s=%s%%a
    > echo %s%
    は、「竹の茂治」とは表示されず、「%s%茂治」と表示されてしまいます。 %a%%a としてバッチファイルで行えば 「茂治」だけが表示されます。

    これは、for 文の局所変数である %a は リストの要素に順に展開されるのに対し、 環境変数の値である %s% は、 この for 文が実行される前に展開されてしまうからで、 その時点では環境変数 s は最初の set s= によって未定義となっていますから、 上の 5. で説明した通り、 コマンドプロンプトでは「%s%」という 3 文字の文字列に展開され、 よって for 文が実行されるときは 既に展開されてしまった文字列 %s% に対して順に


    set s=%s%竹
    set s=%s%の
    set s=%s%茂治
    が実行されてしまうことになり、 よって最後の set コマンドにより s の値は 「%s%茂治」となるわけです。

    この現象は、上のうまくいく例の方の for 文の set コマンドを %j% を使って


    > set j=0
    > for %a in (1 2 3) do set /a j=%j%+%a
    > echo %j%
    と変えても起こり、これも for 文内の %j% が for 文が実行される前に for 文の直前の値である 0 に展開されるので、 for 文では実際には

    set /a j=0+1
    set /a j=0+2
    set /a j=0+3
    が実行されてしまい、結果として 6 ではなく 3 と表示されてしまいます。

    これを防いで、for 文内でも環境変数の値を逐次展開される変数として利用するには、 遅延展開 という機能を利用します。

    デフォルトのコマンドプロンプト (cmd.exe) では、 遅延展開機能が使えるようにはなっていません (off) から、 それを on にする必要があります。 遅延展開機能を on にするには、

    などの方法があります。

    そして、for 文で逐次展開したい環境変数の値は、 %[変数]% ではなく、 ![変数]! のように ! で囲みます。例えば


    > set j=0
    > set s=
    > for %a in (1 2 3) do (
    >   set /a j=!j!+%a
    >   if defined s (set s=!s!%a) else (set s=%a)
    >   rem バッチファイルなら set s=!s!%%a でよい
    > )
    > echo [j=%j%][s=%s%]
    のようにすれば正しく [j=6][s=123] と表示されます。 なお、バッチファイル中では、 上の 5. で説明した通り s が未定義の場合は !s! は空文字になるので、 if defined による場合分けは不要です。

竹野茂治@新潟工科大学
2014年5月2日