batノウハウ

badノウハウならぬbatノウハウです。
忘れたことにやってくるbat作成のために**良さそうな**ノウハウを見つけ次第追記します。
(何かあったら教えてくださいな)

# echoさせない

行頭に`@`を付けても出力が抑制されますが、面倒なので文頭にこれを書いておきましょう。

```bat:sample.bat
@echo off
```

以降のサンプルコードは全て行頭にこれが書いてあるものと思ってください。

# 遅延環境変数を使う

色んな方が説明してくださってるので端折ります。
(特定単位で解釈してるからだとか)
忘れたころに使うとほぼ間違えなくはまります。

```bat:bad.bat
set count=0

for %%a in (1, 2, 3) do (
  set /a count+=1
  echo %count%
)
```

```shell-session:bad_result
> bad.bat
0
0
0
```

```bat:good.bat
rem 遅延環境変数を使えるようにする
setlocal enabledelayedexpansion

set count=0

for %%a in (1, 2, 3) do (
  set /a count+=1
  rem ↓遅延環境変数！
  echo !count!
)
```

```shell-session:good_result
> good.bat
1
2
3
```

どうでもいいですが、謎の魔法ではなく「enable delayed expansion」です。

# 引数

shで言うところの`$1, $2, ... , $@, $*`。
ほぼ同じで、全部の引数を取りたいときは`$*`、各引数は`$1, $2, ...`です。

```bat:sample.bat
echo %1!
```

```shell-session:sample_result
> sample.bat hoge
hoge!
```

# 終了ステータス

shで言うところの`$?`。
`errorlevel`に入ってます。

`exit`の引数として渡した値が終了ステータスになります。

```bat:sample.bat
rem bオプションを付けると呼び出し元プロセスを終了させない
rem (付けないと呼び出し元プロセスまで終了させられる)
exit /b 5
```

```shell-session:sample_result
> sample.bat

> echo %errorlevel%
5
```

# 関数っぽいもの(サブルーチン)

関数は存在しないそうです。
現代っ子プログラマーとしては非常につらいですね…。

ですが、ラベルをうまく利用すると関数っぽく処理を分割して書けます。

```bat:sample.bat
call :hello Hoge
call :hello Huga
call :hello Piyo

exit /b

:hello
echo Hello, %1!!
exit /b
```

```shell-session:sample_result
> sample.bat
Hello, Hoge!!
Hello, Huga!!
Hello, Piyo!!
```

`setlocal`も合わせて使うとなお良さそうですね。

```bat:sample.bat
set one=1

echo %one%
call :local
echo %one%

exit /b

:local
setlocal
set one=2
echo %one%
exit /b
```

```shell-session:sample_result
> sample.bat
1
2
1
```

終了ステータスも返せます。

```bat:sample.bat
call :error
echo %errorlevel%

exit /b

:error
exit /b 1
```

```shell-session:sample_result
> sample.bat
1
```

ただ、これの挙動がちょっと厄介で、shの`$?`と同じように扱うとひどい目に遭います(遭いました)。

```bat:bad1.bat
call :error
echo %errorlevel%
call :success
echo %errorlevel%

exit /b

:success
exit /b

:error
exit /b 1
```

```shell-session:bad1_result
> bad1.bat
1
1
```

これならどうだ！

```bat:bad2.bat
call :error
echo %errorlevel%
call :success
echo %errorlevel%

exit /b

:success
set errorlevel=0
exit /b

:error
exit /b 1
```

```shell-session:bad2_result
> bad2.bat
1
0
```

一見大丈夫そうに見えますが、こうすると…。

```bat:bad3.bat
call :error
echo %errorlevel%
call :success
echo %errorlevel%
call :error
echo %errorlevel%

exit /b

:success
set errorlevel=0
exit /b

:error
exit /b 1
```

```shell-session:bad3_result
> bad3.bat
1
0
0
```

ダメでした。
`errorlevel`変数を自前で定義してしまうと、参照したときにそちらを指してしまって、`exit`に渡した値が取れなくなります。

正解は案外簡単なことで…。

```bat:good.bat
call :error
echo %errorlevel%
call :success
echo %errorlevel%
call :error
echo %errorlevel%

exit /b

:success
exit /b 0

:error
exit /b 1
```

```shell-session:good_result
> good.bat
1
0
1
```

`exit`に明示的に0を渡してあげればよいのでした。

`exit`に何も渡さないと`errorlevel`には何もセットしないので、前にセットされた終了ステータスが延々と引き継がれることになります。
サブルーチンを書くときには明示的に終了ステータスを書くようにした方が安全かと思います。

# 日時

`date`と`time`に入ってます。
それぞれ`yyyy/mm/dd`と`hh:mm:ss.SS`っぽいのでよしなにフォーマットして使いましょう。

```bat:sample.bat
echo %date%
echo %time%
```

```shell-session:sample_result
> sample.bat
2017/07/28
17:30:18.91
```

# IF

## 忙しい人のための早見表

|対象|内容|演算子|
|:--|:--|:--|
|文字列|等しい|A == B|
||等しくない|not A == B|
|数値|等しい|A equ B|
||等しくない|A neq B|
||より大きい|A gtr B|
||より小さい|A lss B|
||以上|A geq B|
||以下|A leq B|
|ファイル・ディレクトリ|存在する|exist A|
||存在しない|exist A|
|変数|定義されている|defined A|
||定義されていない|not defined A|

## 文字列比較

`==`を使います。

```bat:sample.bat
if %1 == hoge (
  echo This is hoge!
) else (
  echo This is not hoge!
)
```

```shell-session:sample_result
> sample.bat hoge
This is hoge!
> sample.bat huga
This is not hoge!
```

## 数値比較

色々あるのでよしなに使いましょう。
心なしか3文字って珍しい気がします(覚えづらい…)。

```bat:sample.bat
rem 等しい
if %1 equ 5 (
  echo %1 is equal to 5!
)
rem 等しくない
if %1 neq 5 (
  echo %1 is not equal to 5!
)
rem より大きい
if %1 gtr 5 (
  echo %1 is bigger than 5!
)
rem より小さい
if %1 lss 5 (
  echo %1 is less than 5!
)
rem 以上
if %1 geq 5 (
  echo %1 is bigger than or equal to 5!
)
rem 以下
if %1 leq 5 (
  echo %1 is less than or equal to 5!
)
```

```shell-session:sample_result
> sample.bat 7
7 is not equal to 5!
7 is bigger than 5!
7 is bigger than or equal to 5!
```

## 存在確認

### ファイル・ディレクトリ

`exist`を使います。

```bat:sample.bat
if exist "%1" (
  echo %1 exists!
) else (
  echo %1 doesn't exist!
)
```

```shell-session:sample_result
> cd
C:\Users\test
> dir
2017/08/08  08:00                   hoge
2017/08/08  08:00    <DIR>          huga
> sample.bat C:\Users\test\hoge
C:\Users\test\hoge exists!
> sample.bat C:\Users\test\huga
C:\Users\test\huga exists!
> sample.bat C:\Users\test\piyo
C:\Users\test\piyo doesn't exist!
```

ファイルとディレクトリの判定をしたい場合は後ろに`\`を付けるといいようです。
ただし、必ず上手くいく訳でもないらしいのでご利用は計画的に。

```bat:sample.bat
if exist "%1" (
  if exist "%1\" (
    echo %1 is directory!
  ) else (
    echo %1 is file!
  )
) else (
  echo %1 doesn't exist!
)
```

```shell-session:sample_result
> dir
2017/08/08  08:00                   hoge
2017/08/08  08:00    <DIR>          huga
> sample.bat hoge
hoge is file!
```

### 変数

`defined`を使います。

```bat:sample.bat
set hoge=hoge
if defined %1 (
  echo %1 is defined!
) else (
  echo %1 is not defined!
)
```

```shell-session:sample_result
> sample.bat hoge
hoge is defined!
```

# 参考

* [バッチファイル界の魔境『遅延環境変数』に挑む（おまけもあるよ）](http://qiita.com/sawa_tsuka/items/c7c477cacf8c97792e17)
* [【bat】Windowsのバッチファイル(*.bat)の書き方自分用まとめ
](http://qiita.com/sksmnagisa/items/8c4c1788af44cc1dc63a)
* [.bat（バッチファイル）のifコマンド解説。](http://qiita.com/sawa_tsuka/items/8edf3d3d33a0ae86cb5c)