バッチファイルの変数のスコープ（試行錯誤）

Y子です。
バッチファイルで使う変数のスコープ（有効範囲）が気になり、調べてみました。
今回は試行錯誤回です。

# 概要
変数の値が、サブルーチンや`setlocal`でどのように変化するか、いろいろ組み合わせて確認します。
_【[確認環境：Windows 10 Home (Bld. 19042.928)](https://qiita.com/koiwaiwaiko/items/ef2d457ae1e1a7c8b698 "Windows10のバージョンを確認・表記する")】_

# コード
## (1) サブルーチンのスコープ
サブルーチンの外で設定した値が、サブルーチンの中でどうなるか。
また逆に中で設定した値が外でどうなるか、の確認です。

```bat:scope_test1.bat
@echo off
rem サブルーチンのスコープ

echo 1-a %var%
set var=aaa
echo 1-b %var%

call :subroutine

echo 3-a %var%
set var=ccc
echo 3-b %var%

exit /b

:subroutine
  echo 2-a %var%
  set var=bbb
  echo 2-b %var%
exit /b
```

```:実行結果
> scope_test1.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a bbb
3-b ccc

> scope_test1.bat
1-a ccc
1-b aaa
2-a aaa
2-b bbb
3-a bbb
3-b ccc
```

`1-b`と`2-a`が一致するので、サブルーチン外の値が中に持ち込まれるようです。
`2-b`と`3-a`が一致するので、サブルーチン内の値が外に持ち出されるようです。
つまり、サブルーチンの内外が、ひと続きの名前空間てことでしょうか。

なお、複数のバッチ間で同じ変数名を使うと、リセットされずに使いまわされてしまいます。
2回目の`1-a`に、1回目の`3-b`と同じ値が入っていることからわかります。
うっかりすると想定外の動作になるので、変数の初期化やローカル化が大切です。

## (2) setlocal/endlocalの効果確認
変数をローカル化させる「`setlocal`」と、ローカル化を終わらせる「`endlocal`」の効果を確認します。

```bat:scope_test2.bat
@echo off
rem setlocal/endlocalの効果確認

echo 1-a %var%
set var=aaa
echo 1-b %var%

setlocal

  echo 2-a %var%
  set var=bbb
  echo 2-b %var%

endlocal

echo 3-a %var%
set var=ccc
echo 3-b %var%
```

```:実行結果
> scope_test2.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a aaa
3-b ccc
```
(1)と比べて、`3-a`だけが変わりました。
`1-b`と`2-a`は一致するので、ローカル化前（`setlocal`実行前）の値が持ち込まれています。
`2-b`と`3-a`は一致しません。`2-b`はローカル化された内部で格納された値ですが、`3-a`はローカル化前の値が復元された値です。
つまり、外部の値は持ち込まれるが、内部の値は持ち出せない、ということに見えます。

## (3) setlocal/endlocalをサブルーチンの外で使う
サブルーチンのコールの外を、`setlocal`/`endlocal`で囲ったらどうなるのか、確認します。

```bat:scope_test3.bat
@echo off
rem setlocal/endlocalをサブルーチンの外で使う

echo 1-a %var%
set var=aaa
echo 1-b %var%

setlocal
  call :subroutine
endlocal

echo 3-a %var%
set var=ccc
echo 3-b %var%

exit /b

:subroutine
  echo 2-a %var%
  set var=bbb
  echo 2-b %var%
exit /b
```

```:実行結果
> scope_test3.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a aaa
3-b ccc
```

(2)と同じ結果になりました。
サブルーチンの内外でスコープが変わらないと(1)で確認してありますので、サブルーチン以外の順番が一致する(2)と(3)は全く同じ処理になると理解できます。

## (4) setlocal/endlocalをサブルーチンの中で使う
サブルーチン内の最初と最後で、`setlocal`/`endlocal`を実行したらどうなるのか、確認します。

```bat:scope_test4.bat
@echo off
rem setlocal/endlocalをサブルーチンの中で使う

echo 1-a %var%
set var=aaa
echo 1-b %var%

call :subroutine

echo 3-a %var%
set var=ccc
echo 3-b %var%

exit /b

:subroutine
  setlocal
    echo 2-a %var%
    set var=bbb
    echo 2-b %var%
  endlocal
exit /b
```

```:実行結果
> scope_test4.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a aaa
3-b ccc
```

これも(2)(3)と同じ結果になりました。
(3)と同様に、(2)と同じ順に処理が行われると理解できます。

## (5) setlocalは通らないと意味がない
念のための確認です。
`setlocal`と書いてはあるが、よく見ると`goto`のせいで通っていないパターンです。

```bat:scope_test5.bat
@echo off
rem setlocalは通らないと意味がない

echo 1-a %var%
set var=aaa
echo 1-b %var%

goto :label_a
setlocal
:label_a

echo 2-a %var%
set var=bbb
echo 2-b %var%

endlocal

echo 3-a %var%
set var=ccc
echo 3-b %var%
```

```:実行結果
> scope_test5.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a bbb
3-b ccc
```

(1)と同じ結果になりました。
`setlocal`を実行していないので、変数のローカル化が行われないという結果です。
まあ、当然すぎて笑っちゃいますが、確認は大事。

## (6) setlocalの替わりに遅延環境変数展開有効
(2)の`setlocal`の行を、環境変数を遅延展開する宣言である`setlocal enabledelayedexpansion`にしたらどうなるか。
同じ「`setlocal`」ですので、同じになるんじゃないですかね。どうでしょう。

```bat:scope_test6.bat
@echo off
rem setlocalの替わりに遅延環境変数展開有効

echo 1-a %var%
set var=aaa
echo 1-b %var%

rem 遅延環境変数の展開を有効にする
setlocal enabledelayedexpansion

  echo 2-a %var%
  set var=bbb
  echo 2-b %var%

endlocal

echo 3-a %var%
set var=ccc
echo 3-b %var%
```

```:実行結果
> scope_test6.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a aaa
3-b ccc
```

(2)と同じ結果になりました。へー！
`setlocal enabledelayedexpansion`後に格納した値は、ローカル化されてたんですね！

## (7) setlocal/endlocalの多重化
`setlocal`の中で、さらに`setlocal`を実行したらどうなるのか！
ローカル化を2重掛けにしてみます。

```bat:scope_test7.bat
@echo off
rem setlocal/endlocalの多重化

echo 1-a %var%
set var=aaa
echo 1-b %var%

setlocal

  echo 2-a %var%
  set var=bbb
  echo 2-b %var%

  setlocal

    echo 3-a %var%
    set var=ccc
    echo 3-b %var%

  endlocal

  echo 4-a %var%
  set var=ddd
  echo 4-b %var%

endlocal

echo 5-a %var%
set var=eee
echo 5-b %var%
```

```:実行結果
> scope_test7.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a bbb
3-b ccc
4-a bbb
4-b ddd
5-a aaa
5-b eee
```

`3-b`と`4-a`が一致しません。2回目のローカル化前の`2-b`の値が復元されています。
`4-b`と`5-a`が一致せず、今度は1回目のローカル化前の`1-b`の値が復元されています。
これはすごいですね！
多重化すると、同じ変数名であっても各階層の値が個別に維持されるようです。

## (8) setlocal/endlocal内の値を外側に渡す
新しい技です（わたし的には）。
`endlocal && set`で、ローカル化された内部の値を持ち出せます。

```bat:scope_test8.bat
@echo off
rem setlocal/endlocal内の値を外側に渡す

echo 1-a %var%
set var=aaa
echo 1-b %var%

setlocal

  echo 2-a %var%
  set var=bbb
  echo 2-b %var%

endlocal && set var=%var%

echo 3-a %var%
set var=ccc
echo 3-b %var%
```

```:実行結果
> scope_test8.bat
1-a
1-b aaa
2-a aaa
2-b bbb
3-a bbb
3-b ccc
```

(1)や(5)と同じ結果ですが、これはローカル化が行われなかったわけではありません。
ローカル化されたうえで、内部の値が外に持ち出された結果です。
関数の戻り値みたいですね。これは使えそう。

#おわりに
いろいろ試しました。
これが何に使えるかはまだ考えてませんが、中の値を外に持ち出して…のような関係がちょっとスッキリしました。
まだ他にもやり方があるんでしょうけどねー。

今後につながる気がします！

では！

# 参考
勉強させていただきました。

https://jj-blues.com/cms/command-setlocalendlocal/

https://iijimas.hatenadiary.org/entry/20101023/1287772847