バッチファイルのforループ内でのERRORLEVEL処理

# 目次

1. [結論](#結論)
1. [forループでの挙動](#forループでの挙動)
1. [参考文献](#参考文献)

# 結論

正常終了以外を検知したい場合、for文の中では、

```bat
if ERRORLEVEL 1 (
    処理
)
```

を使いましょう。

# forループでの挙動

簡単に言うと、forループ内の命令で正常終了しなかった場合でも、%ERRORLEVEL%の値が変わるのはループを抜けた後なので、for文内で%ERRORLEVEL%との大小比較では、欲しい挙動を示してくれないということです。

実際に挙動を比較してみましょう。2通りのバッチファイルを用意します。

1つ目は、for文の中で %ERRORLEVEL% と数値を比較することによって、正常終了しているかどうかを判別するバッチファイルです。

```test1.bat
@echo off

rem 正常終了する命令で ERRORLEVEL を 0 に
python --version
echo before loop ERRORLEVEL: %ERRORLEVEL%
for %%i in (*.py) do (
    python %%i
    echo   %%i ERRORLEVEL: %ERRORLEVEL%
    if %ERRORLEVEL% neq 0 (
        exit /b
    )
)
echo  after loop ERRORLEVEL: %ERRORLEVEL%
echo finish
```

2つ目は、for文の中で if文の ERRORLEVEL を用いた特殊（？）な文法を用いることによって、正常終了しているかどうかを判別するバッチファイルです

```test2.bat
@echo off

rem 正常終了する命令で ERRORLEVEL を 0 に
python --version
echo before loop ERRORLEVEL: %ERRORLEVEL%
for %%i in (*.py) do (
    python %%i
    echo   %%i ERRORLEVEL: %ERRORLEVEL%
    if ERRORLEVEL 1 (
        exit /b
    )
)
echo  after loop ERRORLEVEL: %ERRORLEVEL%
echo finish
```

また、終了ステータス1を返してくれる以下のようなPythonファイルを複数用意します。

```error1~9.py
import sys

sys.exit(1)
```

試してみるディレクトリの中身は、以下のようになります。

```txt
test
  ├─── error1.py
  ├─── error2.py
  ├─── error3.py
  ├─── error4.py
  ├─── error5.py
  ├─── error6.py
  ├─── error7.py
  ├─── error8.py
  ├─── error9.py
  ├─── test1.bat
  ├─── test2.bat
  └─── (test3.bat)
```

準備が整ったところで、早速ですが実行してみましょう。

1つ目のバッチファイルについて、実行結果は以下の通りになりました。

```txt
test>test1.bat
Python 3.9.5
before loop ERRORLEVEL: 0
  error1.py ERRORLEVEL: 0
  error2.py ERRORLEVEL: 0
  error3.py ERRORLEVEL: 0
  error4.py ERRORLEVEL: 0
  error5.py ERRORLEVEL: 0
  error6.py ERRORLEVEL: 0
  error7.py ERRORLEVEL: 0
  error8.py ERRORLEVEL: 0
  error9.py ERRORLEVEL: 0
 after loop ERRORLEVEL: 1
finish
```

pythonプログラムを実行した結果、終了コード1が返されており、%ERRORLEVEL% も1となってループを抜けてほしいのですが、最後まで走り切ってしまっている様子が見て取れます。また、for文から出ると、%ERRORLEVEL% が1となっていることが分かります。

次に、2つ目のバッチファイルについて、実行結果は以下の通りになりました。

```txt
test>test2.bat
Python 3.9.5
before loop ERRORLEVEL: 0
  error1.py ERRORLEVEL: 0
```

for文中で %ERRORLEVEL% を表示させても 0 のままとなってしまっていますが、ERRORLEVEL を用いた分岐処理自体はうまくいっていることが分かります。

さらに、次のようなバッチファイルを実行してみましょう。

```test3.bat
@echo off

rem ERRORLEVEL を 1 に
python error1.py
echo before loop ERRORLEVEL: %ERRORLEVEL%
for /l %%i in (1, 1, 9) do (
    python --version
    echo     count %%i ERRORLEVEL: %ERRORLEVEL%
    if ERRORLEVEL 1 (
        exit /b
    )
)
echo  after loop ERRORLEVEL: %ERRORLEVEL%
echo finish
```

これを実行すると、次のような実行結果が得られます。

```txt
test>test3.bat
before loop ERRORLEVEL: 1
Python 3.9.5
    count 1 ERRORLEVEL: 1
Python 3.9.5
    count 2 ERRORLEVEL: 1
Python 3.9.5
    count 3 ERRORLEVEL: 1
Python 3.9.5
    count 4 ERRORLEVEL: 1
Python 3.9.5
    count 5 ERRORLEVEL: 1
Python 3.9.5
    count 6 ERRORLEVEL: 1
Python 3.9.5
    count 7 ERRORLEVEL: 1
Python 3.9.5
    count 8 ERRORLEVEL: 1
Python 3.9.5
    count 9 ERRORLEVEL: 1
 after loop ERRORLEVEL: 0
finish
```

forループ中では正常終了する命令を実行した場合でも、forループに入る直前で正常終了しない命令を実行してしまうと、こちらも正しくない結果が得られてしまうことも分かります。

以上のように、forループ中での %ERRORLEVEL% の値は、forループに入る直前の %ERRORLEVEL% の値で固定されてしまうので、分岐処理を書く際には気を付けましょう。

# 参考文献

1. [環境変数展開のタイミング（バッチ）](https://blue-red.ddo.jp/~ao/wiki/wiki.cgi?page=%B4%C4%B6%AD%CA%D1%BF%F4%C5%B8%B3%AB%A4%CE%A5%BF%A5%A4%A5%DF%A5%F3%A5%B0%A1%CA%A5%D0%A5%C3%A5%C1%A1%CB)
2. [errorlevel（終了コードを取得する）](https://jj-blues.com/cms/command-errorlevel/)
3. [batコマンドでPythonスクリプトの戻り値を取る際の注意点](https://qiita.com/no-use-kuro/items/78186c2f93190d72cb93)
4. [.bat（バッチファイル）のifコマンド解説。](https://qiita.com/plcherrim/items/8edf3d3d33a0ae86cb5c)
5. [返り値をバッチファイルで受け取るときの注意点](https://qiita.com/OutOfServiceExeption/items/031f9b55f35489624a3b)