おはようございます くろねこです
今回はチョー久しぶりのバッチのはなしです
そうですね
もう3年以上も前に7回にわたり投稿させていただいたバッチ特集の臨時版になります

投稿の経緯
先日、自宅ネットワークの認証系サーバ(ドメインコントローラ 2号機)の動作がおかしいことに気づき調査を進めていました
その際に安定動作させるために、SSDを大容量のものに換装、クリーンインストールを実施、OSも新しいバージョンにこの際なのでアップグレードしました
インストールは問題なく終了したあと
せっかくなので自動複製していた共有フォルダのジョブの見直し&改良(わかりやすくシンプルにする改良)も実現しました
これまで、この自動複製ジョブはバッチでゴリゴリ書いてましたが、このサーバを最初に構築した際に勢いで作ったため、ちょっとわかりにくいコードでした
そこで、コードの一部を汎用的な部品(汎用サブルーチン)として独立させる、所謂「構造化」を実施しました
そのサブルーチンをCALLで呼び出し機能を実現するかたちです
バッチで構想化まで実施することは通常、あまり無いと思います
おそらくこの記事の読者の皆さまもそう思っていると思います
でも、部品化されたサブルーチンは単純機能でバッチが動作する環境なら、サーバもファイルの場所も意識せずに利用できることが可能となり、今後の利用価値の高いものとなるため、「改良」しました
とはいえ、バッチなので一般的なプログラム言語のような高度なことができないため、「ソコソコ」のところで実装します
バッチの構造は以下のとおり
main.bat (WOLで起動、起動時に実行)
① ・・・
② call sub1.bat(自動複製)
③ ・・・
④ call sub2.bat(シャッダウン)
⑤ ・・・
ざっくり組んだところで動作確認すると自動複製は無事実行されました
が、いつまで経ってもシャットダウンされません
sub1.batの自動複製はファイルの状態に変化があったもののみ複製するものでCPUを占有しない仕組みのものです
したがって、共有フォルダ全体の複製処理にはそれなりの時間がかかります
その意識がありますが、いつまで経ってもシャットダウンしません
複製プログラムのログでは複製処理は正常終了しています
③以降が実行されない そういう状態でした
要するに「子バッチから戻らない」ように見えます
これは、検索してもらえれば、すぐに解決方法を見つけることができます
そう、子バッチの終了時の exit の /bオプションが必要ということです
もちろん、子バッチの終了は exit /bと書いてました
となると?
(おさらい) バッチのexit
おさらいになります
exit と exit /bの違いは以下のとおりです
では、くろねこのところで起きてるトラブルは何が起きているのでしょうか?
調査を進め原因が判明!
根気よく調査を進めたところ、子バッチから戻らないのではなく
④ call sub2.bat でバッチファイルが見つからないというエラーが発生してました
(echo offなのでエラーが表示されなかった)

では、なぜ、バッチが見つからない現象が発生したのか?
それは、当ブログのバッチ特集 第1回で説明した cd /d %~dp0 です
(カレントフォルダを実行されているバッチファイルがあるフォルダに変更する)
当然ですが、くろねこが作成するバッチでは処理の冒頭にこのカレントフォルダの変更を呪文のように書いてます
今回のケースの場合、
構造化、汎用化を意識して、部品化されている sub1.batはsub1というサブフォルダに、sub2.batはsub2というサブフォルダに配置し、それら子バッチ(sub1.bat、sub2.bat)は親バッチ(main.bat)から相対パスのかたちで呼び出します
そして、それぞれの子バッチの冒頭でcd /d %~dp0を実行しています
皆さん気づきましたか?
sub1.batを実行し、main.batに処理が戻った時点でカレントフォルダはsub1サブフォルダに変わっています
そのまま処理は流れ、④ call sub2.batの実行で、sub1サブフォルダにはsub2.batは無いので、当然バッチが見つからないエラーが発生するのです
実行環境を復元せよ!
答えは
子バッチは処理終了時にカレントフォルダを呼び出されたときのフォルダに復元すればよいのです
ではどうするのか?
子バッチが呼び出されたときのカレントフォルダは、環境変数%CD%で参照できます これを活用して子バッチの処理を以下のようにします
【子バッチの処理】(改良版)
set @CURRDIR=%CD%
rem カレントフォルダの変更
cd /d %~dp0
子バッチの処理(開始)
・・・
子バッチの処理(終了)
rem カレントフォルダの復元
cd /d %@CURRDIR%
exit /b
OS開発していた時のように(むかし話)
くろねこはメインフレーム(IBM・富士通系)のOS開発を若いころ経験していますが、呼び出されたプログラムが冒頭にSTM命令でレジスタをレジスタ退避域に退避、呼び出し元に戻る前にLM命令でレジスタ退避域からレジスタを復元するのですが、これを思い出しました

これにより、呼び出し元(親プログラム)の動作状態(レジスタ状態)を保証し、OSを構成するすべてのプログラムがこれを厳守するでシステム全体(OS全体)の動作を実現していました
くろねこはアセンブラや専用開発言語でコードを書いていましたが、(当時の)高級言語(COBOLやPL1など)はコンパイラが展開するアセンブラでこのようなコードを展開してました
う~ん 懐かしい
OS開発とバッチ作成は全然規模感が違いますが、
呼ばれた側が親の状態を保証するということは同じですね
要するにプログラミング上の基本中の基本なのかもしれません
この記事をあのときの怖い先輩(N先輩)が読んだら
「くろねこ! お前だけが動けばいいと思ってんじゃねえよ!」と
怒られそうです

N先輩はかなり元やんちゃ系のイケメンで、システム開発の基本を教えていただきました ありがとうございます
どうしているかな? 逢いたいです・・・
おわりに
単なるバッチの話から、若き日の怖い先輩の話まで拡がってしまいましたね(すみません)
バッチ特集はこちらに掲載しています
お時間のある方はお立ち寄りください
