【じゃんけんゲームで入門】Command Prompt

当記事では、自作したじゃんけんゲームを題材にしてCommand Promptの文法や使用するときの注意点などを確認していきます。Windows 10 Home 64bit版環境で検証しています。

ソースコードは以下よりご確認ください。

[rps-like/CommandPrompt at master · tomomoss/rps-like](https://github.com/tomomoss/rps-like/tree/master/CommandPrompt)

## じゃんけんゲームとは

じゃんけんゲームはコンソール上で動作するCUIゲームです。

基本的なルールは既存のじゃんけんそのものです。じゃんけんで勝つごとに1点取得し、対戦相手よりも先に5点先取することで勝者となります。

対戦内容はログファイルに書き出して、後から読み返せるようにします。

## Command Promptとは

Command Promptは **Windows系OSに標準搭載されている「cmd.exe」というシェルの名称であり、また、当該シェルに対応したプログラミング言語の名前** でもあります。

このcmd.exeというシェルはなかなか面白い歴史がありまして、まず、1981年に発表されたWindows系OSの前身となるOSである「MS-DOS」の操作環境をWindows 9.x系で再現した「COMMAND.COM」というシェルがあります。このCOMMAND.COMをWindows NT系に移植するとともに機能を拡張したのがcmd.exeらしいです。

40年近く前に開発された言語であるためか、Commad Promptの **文法や仕様はかなり奇異** なものになっています。当言語独自の仕様などが多く、ハッキリ言って、 **大変に書きにくく読みにくい** ものになっています。さらに、文法を間違えたときに、どこが、どのように間違えているのかを丁寧に教えてくれないので **改修が難しい** という問題もあります。

2006年にMicrosoftから後継シェルとなる「PowerShell」が発表されたこともあり、今後はCommand Promptの使用率はドンドン減っていくことが予想されます。とはいえ、すでにCommand Promptで書かれているものをPowerShellで書き直す必要はない――いや、むしろ、わざわざ書き直したためにデグレードの危険性すらあるので、当分は付き合っていく必要があるでしょう。

## 開発環境構築手順

Command PromptはWindows系OSであれば標準搭載されているはずですので、Windowsユーザーであれば開発環境を構築する必要はありません。逆に、Commad Promptが標準搭載されていない環境（OS XとかLinuxとか）に、後からCommad Promptを導入することはたぶんできませんので、そういった方は諦めてください。

## 注意したい仕様

基本的な文法などはGitHubに上げているソースコードを読んでいただくか、あるいは自前で調べていただくとして――ここからは、GitHubに上げているソースコードからは読み取れない仕様や初めてCommand Promptを触る人に向けての注意事項を列挙します。

### Commad Promptの実行方法

Commad Promptで書かれたプログラムの実行方法は2通りあります。

1つは、Commad Promptを――cmd.exeを立ち上げて対話的に処理を記述していく方法です。言うなれば「対話モード」です。

cmd.exeの置いてある場所ですが、Windows 10 Home 64bit版の場合、cmd.exeは32bit版と64bit版の2種類が標準搭載されています。両者の違いはよく分からないのですが、とりあえず64bit版を優先して使っておけば問題ないはずです。64bit版cmd.exeは（Windows 10をCドライブにインストールしていた場合）C:\Windows\system32\cmd.exeに置いてあります。

32bit版か64bit版か、どちらが起動しているのかを確認したいときは「PROCESSOR_ARCHITECTURE」という特殊な変数を参照してみてください。x86と表示されたら32bit版、AMD64と表示されたら64bit版で動いています。

```:32bit版
C:\Users\tomomoss> ECHO %PROCESSOR_ARCHITECTURE%
x86
```

```:64bit版
C:\Users\tomomoss> ECHO %PROCESSOR_ARCHITECTURE%
AMD64
```

2つ目は、Command Promptが記述されたソースファイル（これはWindowsバッチファイルと呼ばれます）から実行する方法です。こちらは便宜上「バッチモード」と呼称しましょう。

Windowsバッチファイルは **拡張子が「.bat」か「.cmd」である** ファイルのことです。何故2つも拡張子があるのかというと、Windows 9.x系時代には.batが使われていたのですが、なぜかWindows NT系で新たに.cmdでも動くように仕様が拡張されてどちらの拡張子でも動くようになったためです。

どちらの拡張子を使っても問題なく動くのですが、拡張子の違いによって微妙に挙動に違いがあるとかないとか、そういったややこしい議論があったりします。詳しくは以下ページを参照してください。

[windows — Windowsバッチファイル：.bat vs .cmd？](https://www.it-swarm-ja.com/ja/windows/windows%E3%83%90%E3%83%83%E3%83%81%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%EF%BC%9Abat-vs-cmd%EF%BC%9F/958356292/)

Windowsバッチファイルはダブルクリックすることで起動できるほか、コンソールから呼び出して立ち上げることもできます。

### 実行方法による挙動の違い

Command Promptは対話モードで動かすか、バッチモードで動かすかで **微妙に挙動が変わります** 。

たとえば、FORコマンドで用いられる特殊な変数の記述方法に違いがみられます。対話モードの場合は当該変数を表す識別子の前にパーセント記号を1つ付け、バッチモードの場合は2つ付ける必要があります。

```
REM 対話モードの場合はパーセント記号が1つです。
FOR %a IN (*.txt) DO (
    ECHO %a
)

REM バッチモードの場合はパーセント記号が2つです。
FOR %%a IN (*.txt) DO (
    ECHO %a
)
```

また、Bオプション付きのEXITコマンドを実行したとき、バッチモードであればコンソールが閉じますが、対話モードであればコンソールは閉じないなどの違いもあります。

### エントリポイント

Command Promptのエントリポイントはコードの先頭です。CやJavaのようにmain関数やmainメソッドから始まるわけではありません。

### 文字コード

Command Prompt、というかcmd.exeの文字コードは **標準でShift JISになっている** ことに注意してください。ソースファイルの文字コードはShift JISである必要がありますし、入出力されるファイルの文字コードもShift JISで扱われます。

文字コードを操作するにはC:\Windows\System32にあるchcp.comというファイルを使います。このファイルは立ち上げると現在のコンソールで使用されている文字コードをコンソールに出力するプログラムです。

このファイルをコンソールから呼び出すことで現在の文字コードを確認できます。

```
C:\Users\tomomoss> C:\Windows\System32\chcp.com
現在のコード ページ: 932
```

文字コードは **数値で表現される** ことに注意してください。上記の932はShift JISを意味しています。他の文字コードの場合――たとえば、UTF-8の場合は65001となります。

文字コードを変更するにはchcp.comの第1引数に切り替えたい文字コードを表す数値を与えて呼び出します。以下のようにするとShift JISからUTF-8に変更されます。ただし、この変更は当該コンソールが開いている間しか機能しないことに注意が必要です。

```
C:\Users\tomomoss> C:\Windows\System32\chcp.com 65001
```

なお、chcp.comが置かれているディレクトリには標準でPATHが通っていますのでファイル名だけで呼び出すことができます。

```
C:\Users\tomomoss> chcp
現在のコード ページ: 932
```

### キーワードの大文字・小文字を区別しない

Command Promptはキーワードの大文字・小文字を区別しません。ここでいうキーワードとはcmd.exeに組み込まれている命令文（いわゆるコマンド）やパスなどのことです。

その証拠として、以下3つの命令文はいずれも同じ結果を表します。

```
echo Hello world.

Echo Hello world.

ECHO Hello world.
```

### 全ての値は文字列として扱われる

原則として、全ての値は文字列として扱われます（IFコマンドなどでは数値比較ができます）。

SETコマンドで変数に値を入れると、そのことがよく分かります。

```
SET foo=1+2*3
```

この命令文を実行したときに変数fooに入る値は何かというと「1+2*3」という文字列です。「7」という数値ではないんです。もちろん、これはAオプションを付けたうえで代入すると「7」が入るのですが、それも数値の「7」ではなく文字列の7であることに注意が必要です。

```
SET /A foo=1+2*3

REM 7という文字列が標準出力されます。
ECHO %foo%
```

### エコー機能

Command Promptにはエコー機能という仕組みが搭載されています。エコー機能とはプロンプトを命令文ごとに表示するかどうかを制御する仕組みのことです。

標準ではONになっており、ECHOコマンドの第1引数にONかOFFという値を指定することで切り替えることができます。なお、このときに指定するON・OFFという値の大文字・小文字は区別しないことに注意してください。

```
REM 以下のように切り替えます。
ECHO ON
ECHO OFF

REM 大文字・小文字は区別しませんので以下でも切り替わります。
ECHO on
ECHO off
```

また、命令文の先頭にアットマーク記号を置くと、その命令文だけ限定でエコー機能をOFFにすることもできます。

```
REM このままではプロンプトが表示されてしまいます。
ECHO Hello world.

REM これでプロンプトは表示されません。
@ECHO Hello world.
```

### コメント

Command Promptのコメントの扱いはちょっと特殊ですので注意してください。

まず、 **正しいコメントの方法** はcmd.exeに組み込まれたREMコマンドを使用することです。

```
REM ここがコメントです。
```

ただ、他のプログラミング言語に慣れた人にとって、この方法はあまりにも醜い方法に映るでしょう。仕様だと分かっていても感性的に、あるいは感覚的に受け入れられないかもしれません。

そこで、 **代替案** としてコロン記号を使用するという方法を紹介することができます。Command Promptにおいてコロン記号はラベルを定義するためのキーワードなのです。そのため、どれだけラベルを定義しようとも処理自体には何ら影響を与えないためにコメントとして機能するわけです。

```
: 疑似的にコメントを実現できます。
```

ただ、この方法だと誤ってラベルとして呼び出されてしまう可能性が0ではありません（もちろん、かぎりなく0ではあります）。そこで、コロン記号を2つ重ねる方法を提案します。コロン記号を2つ重ねるとラベルでありながら、ラベルとして呼び出せなくなるのです。なお、どうしてコロン記号を2つ重ねると呼び出せなくなるかは謎です。

```
:: これならばラベルとして呼び出される危険性は無くなります。
```

さて、ここまでの説明を聞いたかぎりですと、REMコマンドを使うよりも二重コロン記号を使ったコメントのほうが優れているように思われたことでしょう。実際可読性という観点からは優れているように思うのですが、ラベルは **IFコマンド等のブロック内で定義するとエラーになる** という欠点があります。

証拠として以下コードを提示します。このコードはそのまま動かすと4行目のECHOコマンドが走りますが、3行目のREMコマンドをコロン記号に置き換えてラベル化するとエラーになります。

```
@ECHO OFF
IF 1==1 (
    REM コメントです。
    ECHO 1は1ですよ～
)
EXIT /B
```

### 変数のスコープとSETLOCALコマンド

Command Promptの変数は、他のプログラミング言語でいうところのグローバル変数のように機能します。しかも、 **スコープというものがない** ――自分でスコープを明示的に設定しないと、当該変数が宣言されたプロセス内では常に機能し続けるという厄介な特性があります。

例を挙げましょう。適当にコンソールを開きます。そして以下のように変数を宣言します。

```
C:\Users\tomomoss> SET foo=123
```

さて、これに続けてECHOコマンドで変数fooを参照したらどうなるでしょうか。もちろん、123という値が標準出力されます。

```
C:\Users\tomomoss> SET foo=123

C:\Users\tomomoss> ECHO %foo%
123
```

続けて、以下のようなWindowsバッチファイルを呼び出します

```:test.bat
SET foo=456
```

```
C:\Users\tomomoss> SET foo=123

C:\Users\tomomoss> ECHO %foo%
123

C:\Users\tomomoss> .\test.bat
```

ここで変数fooに格納されている値は123でしょうか、それとも456でしょうか。正解は456なんです。

```
C:\Users\tomomoss> SET foo=123

C:\Users\tomomoss> ECHO %foo%
123

C:\Users\tomomoss> .\test.bat

C:\Users\tomomoss> ECHO %foo%
456
```

このような変数汚染とでも言うべき事態を回避するためにはSETLOCALコマンドを使用する必要があります。ここで再び、先ほどのWindowsバッチファイルを改変して呼び出してみましょう。すると、変数に格納された値が変化していないことが分かります。

```:test.bat
REM 以下処理はコメントアウトします。
REM SET foo=456

REM 新たに以下処理を実装します。
SETLOCAL
SET foo=123
```

```
C:\Users\tomomoss> SET foo=123

C:\Users\tomomoss> ECHO %foo%
123

C:\Users\tomomoss> .\test.bat

C:\Users\tomomoss> ECHO %foo%
456

C:\Users\tomomoss> .\test.bat

C:\Users\tomomoss> ECHO %foo%
456
```

SETLOCALコマンドを宣言すると、SETLOCALコマンドが宣言されてからENDLOCALコマンドが宣言されるまで、あるいは処理末尾に到達するまでの間にスコープが形成されます。このスコープ内で操作した変数は、そのスコープの外部に影響を与えません。

この「影響を与えません」というのは、そのスコープで宣言された変数はスコープ外から参照できないという意味であり、また、そのスコープに入る前に宣言されていた変数の値はスコープ内でどのように弄られようともスコープを出た瞬間にスコープに入る前の状態に戻っているという意味です。

### 特殊な変数

最初から値が入っていたり状況に応じて自動的に値が変わる変数がいくつあります。たとえば、現在の日付が格納されているDATE、現在時刻が入っているTIME、処理の戻り値が入っているERRORLEVELなどです。

詳しくはSETコマンドのヘルプを確認してみてください。

```
C:\Users\tomomoss> HELP SET

C:\Users\tomomoss> SET /?
```

### 文字列の切り出しと置換

Command Promptには数多くの謎仕様と謎機能があるのですが、その1つが文字列の切り出しと置換です。

普通、文字列の操作は関数なりメソッドなりを使用するものですが、Command Promptではどういうわけか変数の機能の一部として実装されています。

詳しくは以下コードを参照してください。

```
REM 切り出す動きは以下の通りです。
SET foo=0123456789

REM 01234
ECHO %foo:~0,5%

REM 12345
ECHO %foo:~1,5%

REM 012345678
ECHO %foo:~0,-1%

REM 8
ECHO %foo:~-2,1%

REM 56
ECHO %foo:~-5,-3%
```

```
REM 置換する動きは以下の通りです。
SET foo=0123456789

REM 0123456789
ECHO %foo%

REM 0!23456789
ECHO %foo:1=!%

REM Hello world.123456789
ECHO %foo:0=Hello world.%
```

なお、切り出しと置換は同時に実行することはできません。

### 変数の代入に注意

変数に値を代入するときはSETコマンドを使用する必要があるのですが、このとき **代入演算子の両辺に半角空白を入れない** ように注意してください。半角空白を入れてしまうと、変数名と値にも半角空白が含まれてしまいます。

```
REM これは駄目です。
SET foo = 123

REM これはOKです。
SET foo=123
```

例外として、Aオプション付きのSETコマンドを使うときは計算式として解釈されるため、半角空白を入れても問題なく動作します。

```
SET /A foo= 1 + 2 * 3
```

### 遅延環境変数

Command Promptの数ある特異な仕様のなかでも際立っているのが遅延環境変数です。

以下記事にて大変丁寧に解説されています。

[バッチファイル界の魔境『遅延環境変数』に挑む（おまけもあるよ） - Qiita](https://qiita.com/plcherrim/items/c7c477cacf8c97792e17#%E6%96%B9%E6%B3%95%EF%BC%91call-set2%E5%9B%9E%E8%A7%A3%E9%87%88%E3%81%95%E3%81%9B%E3%82%8B)

### 標準入力とループ処理を組み合わせるときは初期化すること

標準入力を受け付けるにはPオプション付きのSETコマンドを使用します。

```
REM 一例です。
SET /P input="入力: "
```

この命令文を使用するときに注意しなければならないのは、標準入力された値を受け入れる変数に値が入るのは **何らかの入力があったときだけ** だということです。

以下プログラムは「0」が入力されるまで繰り返し標準入力を求めるようになっています。

```
@ECHO OFF
:loop
SET /P input="入力: "
IF "%input%" EQU "" (
    ECHO 入力されていません
    GOTO loop
)
IF NOT %input% EQU 0 (
    ECHO 0ではありません
    GOTO loop
)
ECHO 入力された値は0です
EXIT /B
```

このプログラムにおいて「0」以外の値――たとえば「1」を入力し、コンソールに「0ではありません」と表示されたのを確認してから、何も入力しなかったらどうなるでしょうか。素直に考えると、何も入力しなかったのですから「入力されていません」というメッセージが表示されそうですが、実際には「0ではありません」というメッセージが再び表示されてしまいます。

この不思議な挙動の原因は、先述したように、標準入力された値を受け取る変数の値が更新されるのは何らかの値が入力されたタイミングのみであるという仕様にあります。

そのため標準入力時――特に適切な値が入るまで何度も何度も入力を求めるような場合は **ループごとに変数を初期化する** 必要があります。

```
@ECHO OFF
:loop

REM 変数を初期化しておきます。
SET input=

SET /P input="入力: "
IF "%input%" EQU "" (
    ECHO 入力されていません
    GOTO loop
)
IF NOT %input% EQU 0 (
    ECHO 0ではありません
    GOTO loop
)
ECHO 入力された値は0です
EXIT /B
```

### 関数の定義はできない

Command Promptでは、他のプログラミング言語でいうところの関数を定義することはできません。

一応、ラベルとCALLコマンド・GOTOコマンドを組み合わせることで、それっぽい挙動は実現できますが色々と問題があります。

```:test.bat
@ECHO OFF
CALL :function_sample 1 2
EXIT /B

REM 第1引数と第2引数を足した合計値を標準出力します。
:function_sample
SET /A foo=%1+%2
ECHO %foo%
EXIT /B
```

```
C:\Users\tomomoss> .\test.bat
3
```

まず、Command Promptで関数を実現するためにはラベルとCALLコマンド、あるいはGOTOコマンドが必要なのですが、そういった処理をジャンプさせるようなプログラムは処理の流れを追うのが難しくなるので **開発も改修も難しくなります** 。いわゆるGOTOプログラミングというやつで、最近では忌避されるプログラミング方法ですね。

加えて、関数では当たり前の存在である戻り値を扱えません。一応、終了コードという整数を返すことはできる（厳密には処理が終わるとERRORLEVELという特殊な変数に終了コードが格納される）のです。

### 終了コード

処理終了時に返る終了コードですが、その値の範囲は-2,147,483,648から2,147,483,647となります（符号付き32bit）。文字列は指定できませんし、浮動小数点数も指定できません。

範囲外の値を指定した場合はエラーにならず、桁溢れ（算術オーバーフロー）が発生しますので注意してください。

### コマンドライン引数

コマンドライン引数は **パーセント記号と0から始まる数値の組み合わせ** で表現されます。

まず、%0には実行しているWindowsバッチファイルのパスが入っています。たとえば、C:\Users\tomomoss\Desktop\test.batを直接立ち上げて%0を参照すると「C:\Users\tomomoss\Desktop\test.bat」という値が返ってきます。ただ、注意したいのはコンソールから立ち上げたときです。このときは相対パスで指定した場合は相対パスが、絶対パスで指定した場合は絶対パスが入っています。

```:test.bat
@ECHO OFF
ECHO %0
EXIT /B
```

```
C:\Users\tomomoss\Desktop> .\test.bat
.\test.bat

C:\Users\tomomoss\Desktop> C:\Users\tomomoss\Desktop\test.bat
C:\Users\tomomoss\Desktop\test.bat
```

そして、%1からは順に引数が入っています。%1には第1引数、%2には第2引数が入っているわけですね。なお、Command PromptにはNULLという概念がないため、引数が指定されていなかった場合は空文字が返ってきます。

なお、ラベルとCALLコマンドを組み合わせて関数っぽく振舞わせたときにも、CALLコマンドの引数が%1以降に格納され、処理から抜けるまで有効になります。

### 比較演算子

比較演算子はIFコマンドのヘルプに記載されています。

```
HELP IF

IF /?
```

私見ですが、比較演算子の1つである **NEQ演算子は使わないほうがよい** と思っています。NEQ演算子に問題があるわけではありません。ただ、NOTキーワードとEQU演算子の組み合わせで代用できますし、NOTキーワードのほうが==演算子でも使えるので、そちらに統一したほうが良いと思うのです。

### 論理演算子はない

Command Promptに論理演算子は実装されていません。

### 空文字の判定

IFコマンドなどで空文字を判定するときはダブルクォート2つで空文字を表現してください。また、判定対象となる値をダブルクォートで囲むようにしてください。そうでないと空文字だったときにエラーになります。

```
SET foo=

REM 悪い例です。
REM 判定対象となる変数fooがダブルクォートで囲まれていません。
IF %foo% EQU "" (
    ECHO 変数fooは空です
)

REM 上記コードが悪いのは、cmd.exe側からはこのように見えているためです。
REM この場合、IFコマンドの構文エラーということになります。
IF EQU "" (
    ECHO 変数fooは空です
)
```

### エスケープシーケンス

Command Promptのエスケープシーケンスはキャレット（あるいはハットマーク）記号です。

### IFブロック内で小括弧を使うときはエスケープすること

IFコマンドのブロック内で小括弧記号を使うときはエスケープする必要があるかもしれません。

以下ソースコードをご覧ください。こちらは変数fooに代入されている値が0であるかどうかを判定し、判定結果に対応したメッセージを標準出力する処理です。

```:test.bat
@ECHO OFF
SET foo=0
IF %foo% EQU 0 (
    ECHO 変数fooは0です(Yes)
) ELSE (
    ECHO 変数fooは0ではありません(No)
)
EXIT /B
```

このプログラムを実行すると「変数fooは0です(Yes)」とだけ出力されるはずですが実際は異なります。

```
C:\Users\tomomoss> .\test.bat
変数fooは0です(Yes
変数fooは0ではありません(No)
```

分かりますでしょうか。両方のメッセージが出力されているうえに「変数fooは0です(Yes)」の末尾閉じ括弧が出力されていないのです。

これはおそらくcmd.exeが以下のようにソースコードを解釈したために起こっているのだと推測できます。

```
@ECHO OFF
SET foo=0
IF %foo% EQU 0 (
    ECHO 変数fooは0です(Yes
)
ECHO 変数fooは0ではありません(No)
EXIT /B
```

そこでコンソールに表示されなかった閉じ括弧をエスケープすることで正常に動作させることができます。

```:test.bat
@ECHO OFF
SET foo=0
IF %foo% EQU 0 (

    REM 閉じ括弧をエスケープします。
    ECHO 変数fooは0です(Yes^)

) ELSE (
    ECHO 変数fooは0ではありません(No)
)
EXIT /B
```

## 私が意識していること

Command Promptを使うときに私が意識していることを列挙します。1つ前の「注意したい仕様」を読んでいることを前提とした記述になっています。

### そもそもCommand Promptの使い道とは

私は、そもそもCommand Prompt（cmd.exe）は **極力使うべきではない** という立場です。後継シェルであるPowerShellが開発される以前であれば仕方ない選択肢だと言えましたが、PowerShellが十分に普及した――PowerShellを標準搭載したOSが十分普及した現在では、わざわざ下位互換であるCommand Promptを使う意味はほぼ無いと断言します。

もちろん、当記事執筆時点においてもPowerShellが標準搭載されていないWindows系OSに頼っているシステムは少なくないでしょう。私が知るかぎりですと、Windows 2000の環境で動いている販売管理システムを知っています。

そういった大変に古い環境では、今からわざわざPowerShellを導入する必要はない、いや、むしろデグレードの危険性を考慮して導入しないほうが良いと思いますが、これから新規開発する場合などはPowerShellを使っていくべきです。

### エコー機能は切っておく

エコー機能は対話的に処理するときは便利なのですが、Windowsバッチファイルを走らせるときは命令文ごとにプロンプトが表示されてしまい、コンソールが大変にごちゃごちゃしますのでソースコードの先頭でエコー機能を切っておくのが無難です。

```
@ECHO OFF

REM 以下にプログラムを書いていきます。
```

### コメントにはREMコマンドを使う

Command Promptではラベル機能を応用してコメントのように振舞わせることができるのですが、私は **REMコマンドを使ったコメント方法を推奨** します。理由は2つあります。

1つは、そもそもこれはラベルであることを表す記号であり仕様なわけで、それをコメントとして扱うのは明らかに邪道だということです。

もう1つの理由は、ラベルはIFコマンド等のブロック内で定義できないということです。そのため、ラベル記号を使ってコメントを書く場合は意識的に書く場所に注意する必要がありますし、ブロック内外でREMコマンドとラベル記号を使い分ける必要も出てきます。そのような些末なことに意識と労力を割くのは無駄でしかありません。

このような理由から、私はコメントを書くときはREMコマンドを使うことを推奨します。

### SETLOCALコマンドは宣言しておく

呼び出し元の変数を汚染しないように、全てのWindowsバッチファイルでは――正確には、変数の操作が行われている全てのWindowsバッチファイルでは、そのソースコードの先頭でSETLOCALコマンドを宣言しておくべきだと思っています。

### 計算式を詰めて書く

SETコマンドでは代入演算子の両辺に半角空白を入れないのが鉄則ですが、Aオプションを付けて計算式として解釈させた場合は数値と演算子を半角空白で区切ることができます。

こちらのほうが可読性に優れているのは確かなのですが、個人的には詰めて書くようにしています。


SETコマンドで変数に値を代入するときは、原則として代入される値は文字列として解釈されます。そのため代入演算子と代入する値の間に半角空白を入れるという一般的なプログラミング言語でのコーディング技術が利用できません。

Aオプションを付けたときは代入する値が文字列ではなく計算式として解釈されるために半角空白を加えることができるのですが、Aオプション付きであるときだけは半角空白を加えても良いとするとコーディングの統一感が薄れてしまうため詰めて書くほうが良い気がします。

```
REM こう書きたくなる気持ちは分かりますが個人的には非推奨です。
SET foo= 1 + 2 * 3

REM 詰めて書く方が好ましい気がします。
SET foo=1+2*3
```

### 関数もどきの取り扱い方

Command Promptには関数を定義する機能はないのですが、ラベルとCALLコマンド・GOTOコマンドを組み合わせることで関数っぽいものを定義することができます。

ただ、この「関数もどき」を使うべきかどうかの判断は **慎重に行うべき** だと思っています。この関数もどきの技術的土台となっているのは悪名高きGOTOプログラミングであり、適切に処理順序を制御できないと思わぬところでプログラミングが止まったり、あるいは止まらなかったりと、それなりに実装費用がかかる印象です。

とはいえ、こんな関数もどきでも基本的な機能が乏しいCommand Promptにとってはありがたい存在であるのも事実です。

以上を踏まえたうえで、私なりの関数もどきの取り扱い方を4つ紹介します。

第1に、関数もどきは **できるだけ使わない方がよい** ということです。正確には、関数もどきを定義する価値が生ずる規模のプログラムをCommand Promptで組もうとするなということです。

第2に、戻り値（終了コード）を **できるだけ利用しない** ことです。関数もどきは-2,147,483,648から2,147,483,647の整数を終了コードという形で返すことができるのですが、浮動小数点数も文字列も返せない以上、戻り値として扱うのはオススメできません。もちろん、戻り値が整数で良い場合もあるのは確かですが、それよりも、そもそも終了コードを利用しないと決めたほうがコードの内容に一貫性がでます。仮に使うとしても0ならfalse、1ならtrue扱いくらいの簡単な判定に留めるのが無難です。

第3に、呼び出し元の変数を操作するような処理は **絶対に実装しない** ことです。呼び出し元の変数を操作するような処理とは、以下のような処理を指します。

```
@ECHO OFF
SETLOCAL

REM 操作前の変数を定義します。
SET foo=1

REM 変数を関数もどきで操作します。
CALL :sample_function %foo%

REM 操作された後の変数を利用します。
ECHO %foo%

EXIT /B

REM ----------------------------------------------------------------
REM 受け取った引数を2倍にした値を変数fooに代入します。
REM
REM %1 2倍にする値の元の値です。
REM ----------------------------------------------------------------
:sample_function
SET /A foo=%1*2
EXIT /B
```

このような処理を走らせてしまうと、処理の流れを――変数の値がどこでどのように変更されたのかを追うのがとても難しくなります。

そのため、全ての関数もどきの先頭ではSETLOCALコマンドを配置し、呼び出し元の変数を汚染しないようにしてください。また、呼び出し元の変数を故意に操作するようなことはやめるようにしてください。

第4に、ドキュメントコメントは必ず書くことです。どこに、どのような関数もどきがあるのかを一瞬で分かるように装飾を付けておいても良いでしょう。一例を以下に載せます。

```
REM ----------------------------------------------------------------
REM 受け取った引数を2倍にした値を標準出力します。
REM
REM ARGUMENTS %1 2倍にする値の元の値です。
REM ----------------------------------------------------------------
:sample_function
SETLOCAL
SET /A result=%1*2
ECHO %result%
EXIT /B

REM ----------------------------------------------------------------
REM 受け取った引数がaであるか判定します。
REM
REM ARGUMENTS %1 判定対象となる値です。
REM
REM ERRORLEVEL 0 aです。
REM            1 aではありません。
REM ----------------------------------------------------------------
:is_a
SETLOCAL
IF NOT "%1" EQU "a" (
    EXIT /B 1
)
EXIT /B 0
```