映画「千と千尋の神隠し」に登場する圧倒的存在感の湯婆婆が千尋の名前を奪うシーンをWindowsバッチファイルで再現してみるという記事です。
わたしの作った湯婆婆の仕様としては
- 名前を入力させる
- 入力した名前からランダムで一文字だけ取り出して表示する
というものです。
記事後半ではコード解説も詳しくしています。
湯婆婆のソースコード
yubaba.bat
@echo off
REM ---------------------------------------------------------
REM 千と千尋の神隠しの湯婆婆をWindowsバッチファイルで再現する
REM ---------------------------------------------------------
setlocal enabledelayedexpansion
REM ----- 最初のセリフ -----
echo 契約書だよ。そこに名前を書きな。
echo;
REM ----- 名前を書く -----
set /p NAME="> "
REM ----- 次のセリフ -----
echo;
echo フン。%NAME% というのかい。贅沢な名だねぇ。
REM ----- 入力した名前の文字列長を得る -----
call :strlen %NAME%
REM ----- 文字列長から乱数を得る -----
call :rand %length%
REM ----- 名前から1文字取り出す -----
set NEW_NAME=!NAME:~%index%,1!
REM ----- 最後のセリフ -----
echo 今からお前の名前は「%NEW_NAME%」だ。いいかい、「%NEW_NAME%」だよ。
echo 分かったら返事をするんだ、「%NEW_NAME%」!
exit /b 0
REM ===== 文字列長をlengthに取得する関数 =====
:strlen
set length=0
set str=%1
:loop
if "%str%"=="" (goto :endloop)
set str=%str:~0,-1%
set /a length=%length%+1
goto :loop
:endloop
exit /b 0
REM ===== 0~Nまでの範囲の乱数をindexに取得する関数 =====
:rand
set N=%1
set /a index=(%random%)%%(%N%)
exit /b 0
endlocal
処理の大まかなイメージ
- Step 1名前を入力させる
変数NAME
- Step 2入力した名前の文字列長を取得する
関数 :strlen, 変数length
- Step 3新しい名前を決定する
関数 :rand, 変数index
- Step 4新しい名前を表示する
変数NEW_NAME
この記事では便宜上、「関数」という言い方をしていますが、Windowsバッチファイルで関数を作成することはできません。
:を使ったラベル機能を使ってメイン処理から:strlenと:randというラベルに飛ばしています。ラベル先頭からexitまでの処理のまとまりを本記事では「関数」と呼ばせて頂きます。
今回のプログラム作成にあたって、入力した名前の文字数を求めてランダムでその中の一文字を取り出す、という処理が必要でした。
分解すると
- 変数の文字列長を求める
- 文字列長を引数にして乱数を発生させる
という処理です。
①はコマンドプロンプト命令には存在しません。関数:strlenとして作成しました。
②は%random%という乱数を取得できるコマンドプロンプトの環境変数を利用しました。
コード解説
コードの先頭から解説していきます。
@echo off
Windowsバッチファイルでは実行するコマンドが全て画面に表示されてしまうため実行結果のみを表示させるための決まり文句です。
REM ---------------------------------------------------------
REM 千と千尋の神隠しの湯婆婆をWindowsバッチファイルで再現する
REM ---------------------------------------------------------
REM~で始まる文は行末までコメント文として扱われます。実行時は無視されます。
その他のREM文も同様です。
setlocal enabledelayedexpansion
遅延環境変数の展開を有効にする設定です。文末にあるend localと対になっています。
setlocal enabledelayedexpansion ~ end local までの処理部分に関して遅延環境変数の展開を有効にするよ、という設定です。
Windowsバッチファイルでは環境変数の値を処理前に展開(あらかじめ代入)してしまうことがあり、途中で値が変わる変数を扱う場合には注意が必要です。今回関数:set_new_nameで利用しています。
「遅延環境変数の展開」ってなんじゃこりゃ?これは難しい言葉です。
正直な所、わたしもそれほど分かっていません。
詳しく知るにはコマンドプロンプト上で help setとして説明を読んでみてください。(少しわかった気になります!)
REM ----- 最初のセリフ -----
echo 契約書だよ。そこに名前を書きな。
echo;
湯婆婆の最初のセリフです。
echo; は、画面上で一行改行したいときに使います。
C言語だとこんな感じの処理と一緒です。
printf("契約書だよ。そこに名前を書きな。\n");
printf("\n");
setは通常変数に値を代入するときに使いますが、/p オプションをつけるとキーボードから入力させる命令になります。
REM ----- 名前を書く -----
set /p NAME="> "
上記コマンドを実行すると、画面上には、 > と表示され、キーボードから入力待ちになります。
入力後にEnterキーを押すと変数NAMEに入力した文字列が代入されます。
REM ----- 次のセリフ -----
echo;
echo フン。%NAME% というのかい。贅沢な名だねぇ。
名前入力後の湯婆婆のセリフです。
echo文に%変数名% とすることで変数内容が表示できます。
先ほどキーボードから入力した名前が%NAME%の箇所に入って表示されます。
たとえば変数NAMEに「ハルヒ」と入っていれば、1行改行とともに
フン。ハルヒ というのかい。贅沢な名だねぇ。
と表示されることになります。
REM ----- 入力した名前の文字列長を得る -----
call :strlen %NAME%
call :strlen %NAME%は、関数:strlenを呼び出す際に変数NAMEを受け渡して呼び出しています。
ここから:strlen関数に処理が移ります。
REM ===== 文字列長をlengthに取得する関数 =====
:strlen
set length=0
set str=%1
:loop
if "%str%"=="" (goto :endloop)
set str=%str:~0,-1%
set /a length=%length%+1
goto :loop
:endloop
exit /b 0
コマンドプロンプト命令には文字列長を求めるコマンドのようなものはありません。
これを肩代わりするため、関数:strlenの処理で変数lengthに入力した文字列長を求めています。
この関数を図示するとこんな感じです。
文字列を数えるために1文字ずつ減らしていき、その都度変数lengthをカウントアップしています。
上記処理はコマンドプロンプトのsetコマンドのいくつかの機能を利用しています。
実はこの関数の解説だけで別記事のボリュームがあります。詳しく知りたい方はそちらを参考にしてください。
ここまでの処理で変数lengthに文字列長が入っている状態となります。
REM ----- 文字列長から乱数を得る -----
call :rand %length%
関数:randを呼び出す処理です。
変数lengthを関数側に受け渡しています。以下の関数処理に移ります。
REM ===== 0~(N-1)までの範囲の乱数をindexに取得する関数 =====
:rand
set N=%1
set /a index=(%random%)%%(%N%)
exit /b 0
set N=%1で、変数Nにlengthの値を代入しています。
set /a index=(%random%)%%(%N%)で、0~(N-1)までの範囲の乱数を発生させて変数indexに代入する処理をしています。
%random%はもともと存在する環境変数で、0~32767の範囲の乱数を発生させます。
算術演算子%は剰余を表します。
例えば、
10%3
は10を3で割った余り(すなわち1)を表します。
setコマンド内で計算をする場合/aオプションをつけますが、その際%文字は特殊な意味をもつため、演算子として%を使う場合%%と記述するようになっています。
よって
set /a index=(%random%)%%(%N%)
の意味は、
変数indexに発生した乱数を変数Nで割った余りを代入する処理ということになります。
変数Nには文字列長が入っているので、文字列長が5であったとしたら、0~4までの範囲の乱数がindexに代入される仕組みです。
REM ----- 名前から1文字取り出す -----
set NEW_NAME=!NAME:~%index%,1!
変数NEW_NAMEに変数NAMEの中にある1文字を取り出す処理です。
バッチファイルの変数には、展開という機能があり、変数内の指定範囲を取り出す機能があります。
記述方法)%変数名:~開始位置,終了位置%
例えば、変数NAMEの先頭から2文字取り出すには、 %NAME:~0,2% と記述します。(文字列の先頭番号は0となっています)
例えば、変数NAMEの中身が「ヤスミ」だったとすると
set NEW_NAME=%NAME:~2,1%
とすればNEW_NAMEには「ミ」が代入されることになります。
イメージ
ただし前述した遅延環境変数の展開を有効にする設定にあったようにWindowsバッチファイルでは環境変数の値を処理前に展開(あらかじめ代入)してしまうことがあり、途中で値が変わる変数を扱う場合には注意が必要です。
そこで今回%記号で変数NAMEをくくらずに!記号でくくり遅延環境変数の展開を有効にしています。
set NEW_NAME=!NAME:~%index%,1!
最後は変数NEW_NAMEに入っている新しい名前を湯婆婆が伝えて終わります。
REM ----- 最後のセリフ -----
echo 今からお前の名前は「%NEW_NAME%」だ。いいかい、「%NEW_NAME%」だよ。
echo 分かったら返事をするんだ、「%NEW_NAME%」!
以上、Windowsバッチファイルでもこういうことが出来るんだ!という気持ちになって頂ければ幸いです。
コメント