コマンドプロンプトで使うタイマー

## 経緯
この記事を作成するまでの経緯は、以下の通りです。
- Googleで「タイマー」と検索して表示されるタイマーの仕様が変わり、劣化したと勘違いした
- これを機にタイマーのプログラムを自作することにした
- Windows環境ですぐ動くものを考え、バッチファイルを選択した
- 軽く調べた限り、タイマーのバッチファイルとして実用に耐えるものがネット上に存在しなかったので、公開することにした
- 記事を書くにあたり、再度Googleで「タイマー」と検索したところ、以前のバージョンと同様にタイマーが鳴るまでの時間をキーボード入力できることが発覚したため、タイマーのバッチファイルの存在意義がほぼゼロになった
## 実際のコード
```batch:timer.bat
@echo off
echo.
echo TIMER
echo.
echo How many minutes later?
set /p minutes=
set /a seconds = %minutes% * 60
timeout /t %seconds%
echo XY | choice > nul
```
**出力例**
```
>timer.bat

TIMER

How many minutes later?
1

 0 秒待っています。続行するには何かキーを押してください ...

>
```
私の環境だと```n 秒待っています。続行するには何かキーを押してください ...```という文面が勝手に表示され、毎秒自動更新されるので、それをタイマーの残り時間表示に流用しています。
しかし、この文面が表示されない環境の人がもしいたら、下の参考ページでも紹介している[こちらのページ](https://takafumi-blog.net/cmd-timer/)を参考に拡張するのもいいかもしれません。
　

**なぜか時間が来たときの音が多少長くなるコード**
```batch:timer_new_window.bat
@echo off
echo.
echo TIMER
echo.
echo How many minutes later?
set /p minutes=
set /a seconds = %minutes% * 60
timeout /t %seconds%
start echo XY ^| choice ^> nul ^& exit
```
試行錯誤の一環として作った、新しいウインドウでタイマーの音を鳴らし、その直後にタイマー用ウインドウを閉じるコードです。
この方法だとタイマー音の鳴動時間が多少伸びますが、その理由はわかりません。
タイマー音として実行しているコマンドは1文字も変化していないのですが……？
まあ、お好みで鳴動時間を変えたいとき用ということで。

## 解説
解説する必要がありそうな行だけ解説していきます。

```batch:
@echo off
```
[こちらの記事](https://qiita.com/Sanada-code/items/5d21d885b732e6fb0d03) が詳しいです。
バッチファイル上の、この行以降(この行を含む)のコマンドを、コマンドプロンプト上に表示させることなく、コマンドの実行結果だけを表示させるためのコマンドです。
これを書かないままバッチファイル上に```echo hoge```と書いた場合、コマンドプロンプト上に```echo hoge```と出力された後、次の行で```hoge```と出力されます。
```echo```の対象になったどんな文字列も2回繰り返すことになり、およそすべての場合で好ましくないので、```echo```が使われるバッチファイルで```@echo off```は必要不可欠のコマンドです。

```batch:
echo.
```
空白行を出力するコマンドです。
[echoコマンドの公式ドキュメント](https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/echo)を読むまで、私も知りませんでした。
空白行や空白に見える行を出力する方法は何通りもありますが、公式ドキュメントに書かれている方法を使うのがやはり一番スマートでしょう。

```batch:
set /p minutes=
set /a seconds = %minutes% * 60
```
変数の設定です。
```set /p```はコマンドプロンプト上で入力待ちの状態になり、入力された文字列を```=```の左辺の変数に代入します。
変数名の直後に(半角スペースを入れずに)```=```を書く必要があることに注意。
```set /a```は```=```の右辺の式の計算結果を左辺の変数に代入します。
変数を読み出す場合、変数名の両側に```%```を付けて表記する必要があることに注意。

```batch:
timeout /t %seconds%
```
変数```seconds```秒間待機するコマンドです。
この状態だと、他のキー入力があった場合にタイムアウトが解除され、タイマー音が鳴ってしまうので、お好みで以下のように書き換えるのもいいと思います。

```batch:
timeout /t %seconds% /nobreak
```
この場合の出力例は次の通り。
```
>timer.bat

TIMER

How many minutes later?
1

 0 秒待っています。終了するには CTRL+C を押してください ...

>
```
出力例の通り、```CTRL+C```を入力しなければタイムアウトが中断しないようになっています。


```batch:
echo XY | choice > nul
```
下の参考ページでも紹介している[こちらのページ](https://yaya.lsv.jp/sound/)に書かれている、音を鳴らすコマンドです。
詳しい解説はそちらに任せます。

前述の「新しいウインドウでタイマーの音を鳴らし、その直後にタイマー用ウインドウを閉じるコード」では、この部分を次のように変えました。
```batch:
start echo XY ^| choice ^> nul ^& exit
```
```start```で新しいウインドウを開き、そこで```echo XY | choice > nul & exit```コマンドを実行するというコードです。

startコマンドでの特殊文字のエスケープ方法は、startコマンドの公式ドキュメントには書かれていませんでしたが、[echoコマンドの公式ドキュメント](https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/echo)に書かれているものを流用することでうまくいきました。
見ての通り、特殊文字の手前に```^```を書くことで、特殊文字がエスケープされ、文字列の一部として扱われます。

また、ここの```&```は、複数のコマンドを1行で表現するためのコマンドです。
(ここで```^&```ではなく```&```と書いてしまうと、元のウインドウで```start```と```exit```が順次実行され、元のウインドウの方が閉じてしまうことになります。)
そして最後に```exit```でウインドウを閉じ、バッチファイルも終了します。

## 参考にしたページ
- [バッチファイルでタイマーを作成する方法](https://takafumi-blog.net/cmd-timer/)
タイトルだけ見ると、これで完成しているから本記事は不要と思われるかもしれません。
実際、このページのコードを叩き台にして、本記事のコードは作られました。
しかし、このページのタイマーは「音を鳴らすコードを省略している」「5秒間で鳴るものしか書かれていない」という点で実用的だとは思えませんでした。
数字の 5 を適宜 180 や 3600 に変えることで、3分や1時間のような実用的な時間に変えられるとはいえ、欲を言えばタイムアウトまでの時間は実行時に設定できるようにしたかったので、本記事のような拡張に至りました。
　
- [コマンドプロンプトから音を出す方法](https://yaya.lsv.jp/sound/)
上のページで音を鳴らすコードが省略されていたので、調べて辿り着いたページです。
音の出し方が3種類紹介されていますが、(私の環境では)今回使った```echo XY | choice```は「ピー」音なのに対し、他の2種類は「デン」のようなインパクトの弱いエラー音であり、他のことをしているときに気付きにくいため、そちらの採用は見送りました。
　
- [echoコマンドのmicrosoft公式ドキュメント](https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/echo)
たかがechoとはいえ、正解の書き方(特殊文字のエスケープ方法を含む)を知ることができたのは割と大きいです。