- 北の空からみなみへ -
exblog staff

<< cmd.exe 変数挙動の確認中 すごく久しぶりに投稿します >>
謹賀新年 そして いまさらコマンドライン(CMD.exe)いぢり
2018年 01月 01日
昨年末にほぼ20年ぶりにバッチと格闘しました。
もっとも30年前~20年前くらいまでの間、AUTOEXEC.bat や CONFIG.sys ばかりいじっていただけです。
それでも仕事がらみで、隔離されたセキュアな環境(つまりWindows標準のものしか使えない)で、最多のマシンがXP(8割)という状態で、ニッチ業務なので自力でやってみたら、大いにハマりました。

やってることは、あるフォルダ配下の複数サブフォルダに画像サーバーから吐き出された同質ファイル=サイズ誤差数十バイトなやつらを、サイズチェックして、一貫性のある名前にして、別途排出のINDEX参照して、IDや性別をファイル名にもたせ、それを転送先に移動し、相手先に通知する。だけなので、まあバッチでできたのですが、その過程でいろいろいろいろ気付いて年末は悶絶してました。

インターネット上の情報にずいぶん助けられ、勉強になったので、少しでも恩返ししようと記事を書くことに。
バッチ職人なんて、かつてのMultiPlan職人ぐらいに先行き滅び去るのでしょうが、まあ、歴史的観点で俯瞰すると似たことってありそうなものなので意味もあるかなって思ったのです。



written @20180101 , modified @20180120
屋上屋を重ねる説明になってるとは思うのだけど、仕方がないかな。。
まわりくどくとも、ハマりがちな思考方向を残さないとなぁ。

初題

(コマンド)
> echo %^path%
(表示)
%path%

(コマンド)
> echo ^%path^%
(表示)
%path%

(for コマンドで引用符)
> for %A in ("%^path%" "^%path^%") do set PATS=%~A&set PATS
(コマンド表示)
> set PATS=%^path% & set PATS
(表示)
PATS=%^path%
(コマンド と 表示)
> set PATS=%path^% & set PATS
PATS=%path^%

引用符にいれてはいけない・・・
ならば引用符に入れなければ?

> for %A in (%^path% ^%path^%) do set PATS=%A&set PATS
(コマンド と 表示)
> set PATS=%path% & set PATS
PATS=%path%
(コマンド と 表示)
> set PATS=%path% & set PATS
PATS=%path%

引用符なしだといけますね。。。
引用符ありでもいきたいところ。

コマンド 変数 [$ ] に%(文字列)を、変数 [$ $] に%%(文字列)を入れる
空白を含む変数にした理由は視認性考慮
> set $ =%&set $ $=%%&&set $
(表示)
$ =%
$ $=%%
わけわか変数を後で一挙に消すのはこんな感じ
> for %p in ("$" "$ " "$ " "$ $" "$$") do set %~p=

続けては for でやる。
2行つかう
> set PATS=%$ %path%$ %&set PATS
(表示)
PATS=%path%
(for 2行目コマンドで引用符ありで入れてみよう)
> for %A in ("%PATS%") do set PATS=%~A&set PATS
(コマンド と 表示)
> set PATS=%path% & set PATS
PATS=%path%

やってることは、<1>変数PATSに、文字列 %path% を格納,<2>その文字列を引用符付きでfor コマンドで渡す(展開時に [~]で削除),<3>内容確認表示
これで %path% という文字列を変数化する方法3種(%^~ と ^%~^ は微妙に意味が異なる)。
これで引用符ありで for 内も達成

さてちょいと問題提起(上記 for で do call set とする前に脳の暖機)

> set $ =%&set PATS=
まずは、変数 [$ ]=% の準備と [PATS] を消去(初期化)してから、
次の1行コマンドでやってることは、<1>[PATS]に文字列 %path% 格納&<2>内容確認&<3>同行call でもいっちょ格納&<4>内容確認表示
> set PATS=%$ %path%$ %&set PATS&call set PATS=%PATS%&set PATS
(<2>の結果表示>)
PATS=%path%
(<4>の結果表示> )
PATS=%path%

それでは 2段重ね call call set 入れて同じくやってみよう

> set $ =%&set PATS=
まず、変数 [$ ]=% の準備と [PATS] を消去(初期化)
> set PATS=%$ %path%$ %&set PATS&call call set PATS=%PATS%&set PATS
(さっきとの違いは、<3>同行であるが call call set としたこと)
(結果表示)
PATS=C:\Program Files (x86) ~ (中略)~ C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem; ~ (後略);

なるほど、ふむふむ。。。二段重ねにして初めて、遅延環境変数(動的変数)っぽくふるまってくれるのね。

初題のテーマは、call call set という二段重ね Tips; のタネ探求ことはじめ.繰り返しっぽく書くけどお許しを

for で変数展開するのと連動する挙動なのでここから初題の本題。(四段重ね)
> set $ =%&set PATS=
変数 [$ ]=% 準備、[PATS] を消去(初期化)
> for %A in ("%$ %path%$ %") do set PATS=%~A&set PATS
(コマンド実行表示 と 結果表示)
> set PATS=%path% & set PATS
PATS=%path%
[PATS] に %path% という文字列を格納できた
> set PATS=
変数 [$ ]不使用、[PATS] のみを消去(初期化)
> for %A in ("%%path%%") do set PATS=%~A&set PATS
(コマンド実行表示 と 結果表示)
> set PATS=%C:\Pro ~ (後略);% & set PATS
PATS%C:\Pro ~ (後略);
この段階で脳が悩みがち。結局、%% と重ね打とうが、引用符で囲い込もうが、環境変数は(存在する限り)展開される。
(キャレットでリテラル? そんなものは環境変数にはないようだ。結果的にそれっぽく見えるので混乱する)
> set $ =%&set PATS=
変数 [$ ]=% 準備、[PATS] を消去(初期化)
(下の for は引用符使ってないので %~A ではなく %A としてある)
> for %A in (%$ %path%$ %) do set PATS=%A&set PATS
(コマンド実行表示 と 結果表示)
> set PATS=%path% & set PATS
PATS=%path%
なかなかにすっきり。。。つうか for in (~~) 内で 引用符なくて空白あるのが、異様な気もするが、変数展開が優先なので平気らしい。
引用符あり("~~")とした場合は、%~A とすれば同じ。
> set $ =%&set PATS=
変数 [$ ]=% 準備、[PATS] を消去(初期化)
> for %A in (%$ %path%$ %) do call call set PATS=%A&set PATS
(コマンド実行表示 と 結果表示)
> call call set PATS=%path% & set PATS
PATS=C:\Pro ~ (後略);
うん。いいぞ。コマンド実行表示で余計な展開もされずすっきり。
一行展開が必要な場合は do call call set で実現。

これで初題の説明終了。
(まあ、コマンドラインからなら何とでも。。。実はバッチからではこううまくはいかない)

次題

(for /f コマンドでパス文字列を分解してみると興味深い)
> for /f "tokens=1-17 delims=;" %A in ("%path%") do echo %~A&echo %~B&echo %~C&echo %~D&echo %~E&echo %~F&echo %~G&echo %~H&echo %~I&echo %~J&echo %~K&echo %~L&echo %~M&echo %~N&echo %~O&echo %~P&echo %~Q
(コマンド表示 と 結果表示)
>echo C:\Program Files (x86)~ & echo C:\WINDOWS\system32 & echo C:\WINDOWS & echo C:\WIND ~ 後略)
(結果表示 前略)
%SystemRoot%\system32
%SystemRoot%
%SystemRoot%\System32\Wbem
%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
(後略)
ほほお~
次題の指摘事項 → パス文字環境変数(%PATH%)の中で一部のパスは %変数% の形で格納されているとわかる
そしてなぜか、抜き出して echo コマンドで表示させているのに、展開されてはいない!!!(かなり驚愕)


三題

外部に用意した設定ファイルから、間接参照の指定が可能とできるか
・・というのが、この正月の課題だったりする。(道楽モンだねぇ)

で、それをやる前に、ちょいと Tips; 披露してみよう。(自慢という名の自己満足)

HTMLのタグをバッチから書き出すのに < や > は、リダイレクト記号として働くので苦慮する。(キャレットでエスケープするのも力任せで破綻した)

そして、ほとんど偶然に書き上がったのが次のコード(一行が長いですがラベル行入れて7行。解説は省略)
:mktble
@remA~Eだがパラメータは %1-%6 の 6個 (%%A = [STUDYINFO]=[ID] = [%1] [%2] )ここは単純に %fName%.t$l へと書き込む.つまり カナ や ローマ字の登録が空の場合は%6のはずの性別が入ってくる
set/a OddEven=$skip%%2
if %OddEven% equ 1 (set OddEven='odd') else set OddEven='even'&remテーブル装飾用です、下は for で回してみて気付いたのですが、項目がわかりやすく書きやすかった
for %%C in ("<tr class=%OddEven%>" "<td class='fname'>%orgFile:~8,4%</td>" "<td class='right'>%2</td>" "<td>%~3</td>" "<td>%~4</td>" "<td>%~5</td>" "<td class='fname'>%newFile%</td></tr>") do set/p<nul=%%C>>%fName%.t$l
echo;>>%fName%.t$l&rem上には改行がないのでここで入れてます
exit/b

リダイレクト記号項目を引用符に入れて、それを for で回して set/p<nul= とやると、素直に横並びで吐き出してくれました。
ちなみに set/p<nul="~~"&rem 行末明示のコメント
とやっても
あるいは set/p<nul=~~&rem
とやっても
引用符がきれいに消えてくれるのは、set/p<nul= で書いた時のメリットですね。

閑話休題

とりあえず、外部の設定ファイルを読み込め、間接参照を含め動作することは確認できました。

ほにゃらか.bat 最初の3行と 該当サブルーチン(間接参照対応版)
@if not "%~1/"=="!/!/" (goto start) else shift&shift&set exec$sub=%2&remonly sub exec[for debug and use tool]
set exec$sub 1>&2&set exec$sub=&goto %0&rem←このgoto以降は戻ってこない
:start

~ 中略 ~

:GET$sets
for /f "usebackq eol== delims=" %%P in (`type %~1^|find "="`) do call call set %%~P&for /f "delims==" %%c in ("%%~P") do set %%c
setlocal enabledelayedexpansion&set msg$txt=!msg$txt:=%%!
endlocal&set msg$txt=%msg$txt%
exit/b&rem環境変数参照を生かすには必ず二重呼出しで [call call] set %%~P とすること最後のfor %%cは単純に結果表示のため付属
rem [important]msg$txt を使用する直前に忘れないこと⇒ call call set msg$txt=%msg$txt%
とりあえずこれで、[msg$txt] 変数についてだけは間接参照できた(万歳)
そのまま代入でダメでも call call set だといけるという挙動がなかったら、これって実現しなかったよなぁ。。

読み込み外部ファイル = ほにゃらか.set
   なにやらいろいろございますが、ご容赦くだされ..
> type ほにゃらか.set
イコールの文字のない行は無視されます
=←行頭にイコールある行はコメントになります.
[;] 以降も読み込まれます.(%PATH% 文字と同様な設定を想定)

このファイル内の値の正当性の検証などは一切していません.
バッチ側はこのファイルからイコールのある行だけを一行ずつ読み込み、単純に set コマンドを発行しています.
このため同じ項目を繰り返すと後の記述が生きるという仕様です.
コマンド連結はご法度です.設定直後に確認用 setコマンドくるので、これは壊れます.

基本の注意
=size$mgn=6886244.100 &rem このようにすると末尾に空白が入った文字が設定されてしまいます
=size$mgn=6886244.100&rem 後から同じ項目あれば、後の方で上書きされます

設定は読み込み直後にその値を表示します.
設定値が不適切な場合では大幅に動きがおかしくなる場合もあります.よって注視を勧めます.

以下にサンプルを交えながら、基本のパラメータ解説をします

~ 前略 ~

変数名[msg$txt] メッセージダイアログに現れる[OK]ボタン直上、×マーク横に表示される短文です.
表示文作成時の件数変数[$Count]や日時変数[date$time]を参照するような指定には マルチバイト文字[] で囲んだ 変数 の形式を使用します.この指定が可能なのは、現在この変数のみとしています。
=msg$txt=御処理願います
msg$txt=$Count 件@ date$time


~ 中略 ~

変数名[fName$Lng]
=ファイルをリネームする頭部側の文字と文字数を [文字(変数).文字長] の形式で指定します.
省略時は年月日[YYYYMMDD]の8字
=fName$Lng=%date:~0,4%%date:~5,2%%date:~8,2%.8
あるいは
=fName$Lng=%%date:/=%%.8

以下のサンプルは凝った使い方の例です.後の方で上書きされる性質を利用
~ 中略 ~
例その2 4行版(消去含めると5行)
[YY(下二桁)MM月名DD]
=/A $$dmy=1%date:~5,2%-101
=/A $dmy*=2
=fName$Lng=睦月如月弥生卯月皐月水無文月葉月長月神無霜月師走
=fName$Lng=%date:~2,2%年%%fName$Lng:~%$$dmy%,2%%.5
=$$dmy=
文字数なので末尾は5です.(やってはダメ全角の2byte換算)
MM消すとファイル名順の並びが乱れる問題が生じるので蛇足的です
実害はないゴミ変数を消したいなら最後(5行目)にこう書けますが、エラーメッセージを見る羽目になります

例その3 ~~の~請求間隔は4ヶ月、その区分をファイル名に反映し一年を3期に分割
[YYYY_1st/2nd/3rd]
/A $dmy=(1%date:~5,2%-101)/3*2
fName$Lng=1st2nd3rd
fName$Lng=%date:~0,4%_%%fName$Lng:~%$dmy%,3%%.8


以上で解説を終了します.

> ほにゃらか !/! GET$sets ほにゃらか.set

上記コマンドで、正常動作確認ができます。。。(ミッションに目途ついた 嬉&疲)
年賀状も書かないで何をやっているのやら。。

[PR]
by bucmacoto | 2018-01-01 22:03 | &Tips;&code; | Trackback | Comments(2)
トラックバックURL : https://bucmac.exblog.jp/tb/28872130
トラックバックする(会員専用) [ヘルプ]
Commented by jemini-web at 2018-01-03 17:34
うおう。運用管理系の人なんで、わりかしバッチは使うんですが、こんな複雑な事は出来ないなあ。
自分だったらVBscript(WSH)に逃げちゃいますね。(でもPowerShellの勉強はしないものぐさ)

と、コメント先行しましたが、明けましておめでとうございます~
Commented by bucmacoto at 2018-01-03 23:24
あ、ジェミニさん。おめでとうございます。
コメントありがとうございます。

VBscriptは全く使った経験ないので、今回、送信先にポップアップ出すワンライナーをバッチから吐き出して使っただけです。

つうかこのスキン、コメ欄がおかしくなってますね。放置しすぎてたので手を入れないとかもです。。
名前
URL
画像認証
削除用パスワード
<< cmd.exe 変数挙動の確認中 すごく久しぶりに投稿します >>
<< cmd.exe 変数挙動の確認中 すごく久しぶりに投稿します >>