バッチファイルの基本的な書式と考え方

基本的なバッチファイルの書き方と項目ごとの特徴をまとめました。


## 基本的なバッチファイル（共通）の書き方
バッチファイルの内容に関わらず一般的によく使われるコマンドです。最低限、覚えておいた方が良い項目です。


```bat:sample.bat（基本的な共通コマンド）
@echo off

rem 遅延環境変数の使用を宣言
setlocal enabledelayedexpansion

rem カレントフォルダへ移動
cd %~dp0

rem 実行中のバッチファイルを一時停止
pause
```

### 実行したコマンドを非表示にする
先頭に`@echo off`を記述することで、バッチファイル内のコマンドを実行結果に含めないようになります。
先頭に`echo off`と定義するだけでは、最初の`echo off`だけ表示されてしまうため、それを防ぐために先頭に`@`を加えて、`@echo off`と定義します。
### 遅延環境変数の使用を宣言する
バッチファイルには`環境変数の即時展開`という特徴があり、環境変数が読み込まれた時点で値を確定してしまいます。
環境変数の即時展開を防ぐためには`遅延環境変数`を使用します。

1. `setlocal enabledelayedexpansion`を定義する
1. 変数を`%`ではなく`!`で囲む

条件分岐（if文）や繰り返し（for文）などを使用する時によく使います。

### カレントフォルダへ移動
`cd`コマンドで`%~dp0`を指定すると、対象のバッチファイルが保存されているフォルダへ移動します。


### 一時停止する
バッチファイル内に`pause`を記載すると、実行中のバッチファイルの処理を一時停止できます。
`pause`は主に作成中のバッチファイルの動作確認などのために使用します。


### コメントアウトの記載
バッチファイル内に`rem`を記載すると、コメントを追加できます。

:::note info
バッチファイルには複数行をまとめてコメントアウトする方法が無いため、1行ずつコメントアウトする必要があります。
:::

## 変数の取り扱い方
一般的な変数の取り扱い方をまとめました。

### 変数の宣言と展開
変数を定義するには`set`コマンドを使います。`set`コマンドのオプションには以下のようなものがあります。

|オプション|意味|
|---|---|
|なし|変数に値を代入|
|/a|変数に計算結果を代入|
|/p|変数に入力値を代入|

変数を展開するには、変数を`%`で囲みます。ただし、`for`文や`if`文などの中で遅延環境変数を展開する場合は`%`ではなく`!`で変数を囲みます。

```bat:variables.bat（変数の取り扱い）
set a=1
set b=2
set /a c=a*b
echo %c%

set /p d=数字を入力して下さい。：
echo 入力された数字は%d%です。
```

:::note warn
変数を宣言する場合、スペースを含めると設定値が正しく代入されません。
:::


### 変数の値を参照
変数の値を参照するには`echo`コマンドで変数を`%`で囲みます。
複数の変数を結合する場合も同様に`%`で囲むことで結合が可能です。
以下の例では`date`コマンドと`time`コマンドを数字の部分を抽出した結果を出力しています。

```Console
C:\>echo %date:~0,4%%date:~5,2%%date:~8,2%%time:~0,2%%time:~3,2%
202110152345
```

具体的には以下のような情報を抽出し、結合しています。

1. `date`コマンドの先頭から4文字を抽出
1. `date`コマンドの6文字目から2文字を抽出
1. `date`コマンドの8文字目から2文字を抽出
1. `time`コマンドの先頭から2文字を抽出
1. `time`コマンドの4文字目から2文字を抽出

※2021年10月15日23:45の実行結果です。

:::note info
文字数の数え方は1からではなく0から開始します。
:::

### 変数の切り出し（加工）

|書式|意味|
|---|---|
|%a%|変数全体|
|%a:~m%|先頭m文字目から最後まで|
|%a:~m,n%|先頭m文字目からn文字分まで|
|%a:~m,-n%|先頭m文字目から後尾n文字分まで|
|%a:~-m,n%|後尾m文字目からn文字分まで|
|%a:aaa=bbb%|変数aの文字列aaaを文字列bbbに置き換え|

### 特殊な変数

|変数|意味|
|---|---|
|%cd%|カレントフォルダのパス|
|%date%|現在日付|
|%time%|現在時刻|
|%random%|ランダムな値|
|%errorlevel%|直前のコマンドのエラーレベル|


### ローカル変数の定義
ローカル変数を定義することで、変数の利用範囲を限定できます。

|コマンド|意味|
|---|---|
|setlocal|ローカル変数の使用を開始|
|endlocal|ローカル変数の使用を終了|

先述した遅延環境変数を定義する場合も、`setlocal`コマンドを使います。
バッチファイルの先頭に`@echo off && setlocal`と定義することも可能です。

### 引数の指定
バッチファイルを実行する際に引数を指定することができます。

|変数|意味|
|---|---|
|%0|ファイル名|
|%1|一つ目の引数|
|%2|二つ目の引数|
|%*|すべての引数|

引数として利用できる引数は%1から%9の9個までです。

```bat:argument.bat（引数の指定）
@echo off

echo ファイル名は%0です。
echo 一つ目の引数は%1です。
echo 二つ目の引数は%2です。
echo このプログラムの全ての引数は %* です。
```
実行結果は以下の通りです。

```
C:\>argument.bat args1 args2 args3
ファイル名はargument.batです。
一つ目の引数はargs1です。
二つ目の引数はargs2です。
このプログラムの全ての引数は args1 args2 args3 です。
```

:::note info
引数となるファイルが一つの場合、バッチファイルにドラッグ&ドロップすることで、一つ目の引数として認識され、バッチファイルが実行されます。
:::

### 対話型コマンドの自動入力

`echo`コマンドとパイプ（`|`）を活用することで対話型コマンドの入力を自動入力にすることができます。

```
C:\>echo y | del folder
C:\folder\*、よろしいですか (Y/N)? y
```
`del`コマンドを実行すると、通常、`(Y/N)`のどちらかを入力する必要がありますが、`echo y`コマンドとパイプ（`|`）を活用することで、自動入力が可能です。

:::note info
対話型コマンドの自動入力はLinux（bashなど）でも利用可能です。
:::




## 条件分岐（if文）
条件分岐には`if`文を使用します。

```bat:sample_if.bat
@echo off

set /p num=１または２の数字を入力して下さい：

if %num% equ 1 (
	echo 入力された値は%num%です。
) else if %num% equ 2 (
	echo 入力された値は%num%です。
) else (
	echo 入力された値は%num%です。正しい値を入力して下さい。
)
```

数値を比較する場合は比較演算子を使います。主な比較演算子は以下の通りです。

|比較演算子|意味|由来|
|:-:|:-:|:-:|
|equ|等しい|equal|
|neq|等しくない|not equal|
|gtr|より大きい|greater then|
|lss|より小さい|less then|
|geq|以上|greater equal|
|leq|以下|less equal|
|defined|環境変数が設定されている|ー|
|not defined|環境変数が設定されていない|ー|

文字列の比較は以下の通りです。

|比較|意味|
|:-:|:-:|
|文字列A == 文字列B|文字列Aと文字列Bは等しい|
|not 文字列A ==　文字列B|文字列Aと文字列Bは等しくない|

数字の比較も可能ですが、あくまでも文字列として比較されます。数値の大小を比較する場合は`equ`や`neq`を使います。

### ファイルの有無による条件分岐

if文と組合わて便利なコマンドに`exist`や`errorlevel`などがあります。

|コマンド|意味|
|:-:|:-:|
|exist|ファイルやフォルダの有無を判断する|
|errorlevel|直前の処理結果を判断する|


`exist`コマンドによって、ファイルやフォルダの有無を判断します。
ファイルやフォルダが存在するかしないかによって、後続の処理を分岐します。

`errorlevel`コマンドにより直前の処理結果をもとに条件分岐が可能です。正常終了か異常終了かによって、後続の処理を分岐します。

### ディレクトリセパレータによる判定
引数がファイルかフォルダかを見分けるには、引数の末尾にディレクトリセパレータである`\`を付けることで判定します。


```bat:sample_exist.bat
@echo off

set folder_name=%1
if exist %folder_name%\ (
	echo フォルダが選択されました。
) else (
	echo フォルダ以外が選択されました。
)
```


## 繰り返し（for文）
繰り返し処理にはfor文を使います。


```bat:sample_for.bat
@echo off

rem 変数が1から9まで間、2ずつ値を追加していく
for /l %%a in (1,2,9) do (
	echo %%a回目の繰り返し処理です。
)
```

実行結果は以下の通りです。

```
1回目の繰り返し処理です。
3回目の繰り返し処理です。
5回目の繰り返し処理です。
7回目の繰り返し処理です。
9回目の繰り返し処理です。
```

## 繰り返し処理の中での条件分岐
繰り返し処理の中で条件分岐をする事も可能です。サンプルプログラムの仕様は以下の通りです。

1. カレンドフォルダ内のファイル名を確認する
1. 格納用フォルダとしてファイル名の先頭3文字のフォルダを作成する
1. 格納用フォルダが既にある場合はフォルダ作成をスキップする
1. 対象ファイルを格納用フォルダへ移動する

```bat:rename.bat
@echo off

rem バッチファイルのカレントフォルダへ移動
cd /d %~dp0

rem 遅延環境変数の使用を宣言する
setlocal enabledelayedexpansion

rem フォルダ無いのテキストファイル(拡張子.txt)を全て読み込む
for %%f in (*.txt) do (
	rem ファイル名の先頭3桁を取得
	set filename=%%f
	
	rem 遅延実行変数のため変数を[!]で囲み、先頭から3文字を抽出
	set foldername=!filename:~0,3!

	rem 先頭3文字のフォルダが既に作られていたらフォルダ作成をスキップ
	rem 先頭3文字のフォルダが無ければ新規作成
	if not exist !foldername! (
		mkdir !foldername!
	)

	rem ファイルを移動
	rem コマンドの実行結果を非表示
	move !filename! !foldername! > nul

)
```


## サブルーチン
`call`コマンドでサブルーチンを呼び出すことができます。
### 単一ファイルによるサブルーチン

```bat:subroutines.bat
@echo off

set a=2
set b=4
set c=10

rem メインの処理
call :sub01
call :sub02 %a% %b%

echo 変数[c]の値は、初期値として設定された%c%のままです。
exit /b

rem サブルーチン１の処理
:sub01
echo サブルーチン１のechoコマンドです。
exit /b

rem サブルーチン２の処理
:sub02
setlocal
echo サブルーチン２のechoコマンドです。
echo 一つ目の引数は変数[a]です。変数[a]の値は%1です。
echo 二つ目の引数は変数[b]です。変数[b]の値は%2です。

rem ローカル変数を定義しているため変数[c]の計算結果はサブルーチン２の中でだけ有効
set /a c=%1*%2
echo 引数を元に計算(掛け算)した結果、変数[c]の値は%c%になりました。

exit /b
```

実行結果は以下の通りです。

```
サブルーチン１のechoコマンドです。
サブルーチン２のechoコマンドです。
一つ目の引数は変数[a]です。変数[a]の値は2です。
二つ目の引数は変数[b]です。変数[b]の値は4です。
引数を元に計算(掛け算)した結果、変数[c]の値は8になりました。
変数[c]の値は、初期値として設定された10のままです。
```
上記のサンプルではサブルーチン内でローカル変数`setlocal`を定義しているため、サブルーチン内での計算結果はメイン処理には反映されません。

### 複数の外部ファイルによるサブルーチン


```bat:main01.bat(メインのファイル)
@echo off

echo メインファイルによるechoコマンドです。
call sub01.bat
call sub02.bat
```

```bat:sub01.bat(サブルーチン１のファイル)
@echo off

echo 外部ファイル[sub01.bat]によるechoコマンドです。
```


```bat:sabu02.bat(サブルーチン２のファイル)
@echo off

echo 外部ファイル[sub02.bat]によるechoコマンドです。
```


実行結果は以下の通りです。

```
メインファイルによるechoコマンドです。
サブファイル[sub01.bat]によるechoコマンドです。
サブファイル[sub02.bat]によるechoコマンドです。
```

## まとめ
自身の課題を解決するためのメモなので、内容には偏りがあり網羅性は低い内容ですが、最低限のバッチファイルを作成するには以下の知識を身に付ければ対応可能だと思います。

- コメント、一時停止、遅延環境変数などの共通で使う機能を理解する
- 変数の扱い方と文字列を加工する仕組みを理解する
- 引数、サブルーチン（関数）を有効に活用する
- 条件分岐、繰り返しの基本書式と考え方を理解する

ただし、バッチファイルでは実装できる内容に限界がある場合もあるため、可能であれば、PowerShellなども併用して課題解決に取り組む方が効率的だと思いました。