2016 10 28

バッチファイルの引数

たくさんあるファイルの中から、ある条件に該当するファイルだけに操作をしたい場合がある。 割と頻繁にある。

そんな場合、条件や操作が単純なら forfiles で、複雑な場合はバッチファイルを二つ組み合わせて実行していた。

複雑ってのは、例えばこんな感じ。

二つというのは、ファイルに操作をするためのバッチファイルと、そのバッチファイルに操作対象のファイルを引き渡すためのバッチファイルで、要は forfiles 相当を自前で実装した形。

コマンドなら一つなのにバッチファイルが二つなのは、処理対象のファイルの名前や日付やサイズを調べるにはバッチファイルの引数にする必要があると思っていたから…

と、抽象的に言っても分かり難いので具体的に。 上で挙げた複雑な場合の例を実装してみよう。

mv_ctrl_01.bat

set RCV="C:\Sample Files\smpl01\rcv" set WRK="C:\Sample Files\smpl01\wrk" set BAK="C:\Sample Files\smpl01\bak" for /r %RCV% %%i in (*.txt) do ( cmd /c mv_rcv2wrk.bat "%%i" )

mv_rcv2wrk.bat

set N=%~nx1 if exist %WRK%\%N% ( cmd /c mv_wrk2bak.bat %WRK%\%N% ) move %RCV%\%N% %WRK%

mv_wrk2bak.bat

set T=%~t1 set T=%T:/=% set T=%T::=% set T=%T: =% move %WRK%\%N% %BAK%\%N%.%T%

大して考えずに簡単な複雑な例を挙げたのだが、これが予想外に複雑だった。 おかげでバッチファイルが2つじゃなくて3つになってしまったが、まあこれはこれでいいだろう。 mv_ctrl_01.bat を実行すると、受信フォルダの中にある拡張子が 「.txt」 のファイルを全て処理する。

ポイントは、下請けの mv_rcv2wrk.bat と、孫請けの mv_wrk2bak.bat の最初の行。 どちらも処理対象がバッチファイルの第1引数として設定されるものとして、そこからファイル名や拡張子やタイムスタンプを取り出している。

こんな構成になっているのは、ファイル名や拡張子やタイムスタンプを取り出すための書式がバッチの引数に対して適用する形になっているから。 逆に言うと、処理対象を引数という形にするために、呼び出す側と呼び出される側とに分割する必要があったから。

…と思っていたからなのだが、実はこれ、サブルーチンでも有効なんだよね。 バッチファイルでサブルーチンを使うことがほぼないために忘れがちだが、というか最近まですっかり忘れていたのだが、全く同じ形式で使えるのだ。

上の例なら、バッチファイルを分割しなくても、こんな感じで1つのバッチファイルで実装できる。

mv_ctrl_02.bat

set RCV="C:\Sample Files\smpl01\rcv" set WRK="C:\Sample Files\smpl01\wrk" set BAK="C:\Sample Files\smpl01\bak" for /r %RCV% %%i in (rcv*.txt) do ( call :mv_rcv2wrk "%%i" ) exit :mv_rcv2wrk set N=%~nx1 if exist %WRK%\%N% ( call :mv_wrk2bak %WRK%\%N% ) move %RCV%\%N% %WRK% goto :EOF :mv_wrk2bak set T=%~t1 set T=%T:/=% set T=%T::=% set T=%T: =% move %WRK%\%N% %BAK%\%N%.%T% goto :EOF

バッチファイルが下請け孫請けのバッチファイルを呼び出す構成の場合、それぞれがどこにあるかを気にする必要があるが、一つならそんな心配は無用。 変数の定義を確認するために他のファイルを開く必要もない。 若干行数が増えることがデメリットだが、管理が楽になるというメリットの方がぐっと大きいだろう。

何でもそうだが、綺麗さっぱり忘れていると、忘れているということに気付かないまま覚えていることだけで進めてしまうんだよな。 「忘れている」 が 「知らない」 でも同じ。 知ってる範囲のことだけで進めて、後になって、こうすればもっと巧くできたのにと反省するのだ。

まあ、そんな反省を最近したってことなのだが。

せっかくだから、引数から取り出せる情報をまとめておこう。 適用はバッチでもサブルーチンでも同じ。

書式は下記が基本。

「%~」+「取り出したい情報に対応する文字(複数列記可能)」+「何番目の引数かを示す数字1桁」

dir コマンドの実行結果が

C:\Sample Files\smpl01 のディレクトリ 2016/10/27 22:31 <DIR> . 2016/10/27 22:31 <DIR> .. 2016/10/28 09:33 1,024 test.txt.bak

となるファイル test.txt.bak を第1引数にフルパスで "C:\Sample Files\smpl01\test.txt.bak" として指定した場合の適用結果と併せて以下に示す。

%1 "C:\Sample Files\smpl01\test.txt.bak" 第1引数全体。 途中の空白対策の「"」を含む。
%~f1 C:\Sample Files\smpl01\test.txt.bak フルパスファイル名。 空白対策の「"」は含まない。
%~d1 C: ドライブレター。 コロンも含む。
%~p1 \Sample Files\smpl01\ フォルダ名。 末尾はパス区切り文字。
%~n1 test.txt ファイル名。
%~x1 .bak 拡張子。 末尾から見て最初の「.」まで。
%~z1 1024 ファイルのサイズ。 単位はバイト。
%~t1 2016/10/28 09:33 タイムスタンプ。 日付と時刻の間に半角空白1文字。

この他に属性情報も 「%~a1」 として取り出せるのだが、Windows環境で使うことがあるとは思えないので省略。

第2引数の場合は、上記の1を2に置き換えればいい。 0はバッチファイル自身の情報となる。

書式から察する通り、直接指定できるのは0から9まで。 つまり引数なら9個までなのだが、バッチファイルとしては10個以上の引数を指定することも可能。 そんな10番目以降の引数を扱いたい場合は shift と併用する。

各要素を列記して一括取得することが可能だが、どの情報を取得するかの指定はできても、取得する情報の順番や数は指定できない。 同じ情報の繰り返しは1回に集約され、最終的には順番も込みで 「タイムスタンプ サイズ フルパスファイル名」 という形になる。 タイムスタンプやサイズが他の要素と共に指定された場合、間に半角空白が1つ入る。

例えば 「%~tzf1」 も 「%~tzdpnx1」 も 「%~ddzxpf1」 も、結果は全て同じ。 上の例なら 2016/10/28 09:33 1024 C:\Sample Files\smpl01\test.txt.bak となる。

どうしても同じ情報を繰り返したい場合は、列記せずに個別に繰り返せばいい。 「%~ddd1」 とすると 「C:」 だが、これを 「%~d1%~d1%~d1」 とすれば 「C:C:C:」 となる。 まあ、そりゃそうだよなって話だが。