任意フォルダ配下から、特定ファイルをフォルダ構造ごと収集するバッチ処理

ソフトウェア
OpenIconsによるPixabayからの画像

業務で、Webサーバからバッチファイルを収集するバッチを作る機会がありました。その時のバッチ処理を、もう少し一般化して、今回の記事にまとめました。

バッチファイル収集作業の詳細

職場に各プログラムのソースコードを、バージョン管理ソフトで管理する流れがあり、Webサーバのバッチファイルも管理対象になりました。しかし業務ソフト等のソースコードと違い、Webサーバのバッチファイルは、必要に応じて作成・配置する感じで拡張されてきたので、あちこちのフォルダに散在していました。なので、ソースコード管理の為、Webサーバのバッチファイルを、一か所に集めるDOSバッチを作成する事になりました。

DOSバッチ化したい事

バッチ化したい事をざっくり書き出しました。

  1. Webサーバの本体は、Dドライブ配下にあるので、バッチファイルはドライブ単位で検索する
  2. バッチの配置場所が分からなくなるので、バッチファイル取得時、配置したフォルダ構造ごと取得する
  3. バッチを複数回動かす時、取得ファイルが同一なら、ファイルが更新されていた時だけ取得する
  4. 取得フォルダの構造が複雑にならないよう、バッチファイルがある場合のみ取得する
  5. バッチの検索・取得先、保存先とも任意のフォルダを指定できるようにする

作成したDOSバッチ

以上を踏まえて、以下の様なDOSバッチ(get_bat_files.bat)を作成しました。

get_bat_files.bat
@echo off
rem 実行されているバッチが置かれているフォルダをカレントディレクトリにする
cd /d %~dp0
rem ローカル変数使用開始
setlocal
rem 変数設定
set targetext=bat
set indir=D:\
set outdir=.\out

rem 出力先フォルダが無ければ作る
if not exist %outdir% mkdir %outdir%

rem メッセージ表示せずに3秒待機
timeout /t 3 /nobreak > nul

rem for /fオプション 変数名 in (ファイル名) do コマンド
rem /fオプション:ファイルまたはコマンドの実行結果を行単位で変数に読込む
rem --------------------------------------------------------------------
rem xcopy       :ファイルをフォルダ構造ごとコピー
rem /dオプション:コピー先に同名のファイルが存在する場合に更新日が新しいファイルのみコピーする
rem /iオプション:コピー先のディレクトリが存在しない場合は新規にディレクトリを作成する
rem /sオプション:ファイルが存在する場合のみディレクトリごとコピーする
rem /yオプション:同名のファイルが存在する場合、 上書きの確認を行わない
for /f %%a in ("%indir%\*.%targetext%") do xcopy "%%a" "%outdir%\" /d /i /s /y

rem ローカル変数使用終了
endlocal

rem 一時停止「続行するには何かキーを押してください… 」を表示して処理を抜ける
pause
@echo on

コマンド説明

以前に作成したバッチと被る箇所については、その時の記事を参照下さい。

「cd /d %~dp0」定型文

「 cd /d %~dp0 」は、バッチファイルの中でよく見かけるくだりです。「 cd 」コマンドは、ディレクトリを変更するコマンドで「 /d 」は、ドライブを変更する時に、指定するオプションです。次に「どこに変更したいのか」の部分が「 %~dp0 」になります。「 %~dp0 」は「 %0 」にオプション構文の「 ~ 」と「 d 」と「 p 」が付いたものです。各オプションの説明は以下の通りです。

  • 「%0」:バッチの特殊な引数参照で「起動されたバッチファイルのフルパス」を格納する
  • 「~」:引数参照で格納された文字列から”(ダブルクオート)を除く
  • 「d」:引数参照で格納された文字列からドライブ文字を取得する
  • 「p」:引数参照で格納された文字列からファイル名を除くパスの部分を取得する

以上を総合して「 %~dp0 」は「実行されているファイルが置かれているディレクトリ」を表します。挙動確認の為、以下のようなバッチファイルを作成しました。

@echo off
echo カレントディレクトリ1
cd

rem バッチファイルのあるデイレクトリへ移動
cd /d %~dp0

echo カレントディレクトリ2
cd
@echo on

以上を例えば「test01.bat」ファイルにまとめて、任意フォルダ(C:\temp\test\batch_dir)に置きます。このバッチをコマンドプロンプトから起動すると、以下のようなプロンプト画面が表示されます。カレントディレクトリが、実行ファイルが置かれているディレクトリに変更(「C:\Users」→「C:\temp\test\batch_dir」)されたことが確認できます。

「if not exist ~」構文

if not exist ~」は、バッチファイルで条件分岐を使う「if」コマンドの基本構文(if 条件式 コマンド)です。ここでは「if not exist 変数参照(出力先フォルダ名) mkdir 変数参照」としました。変数に設定した出力先フォルダが存在しなかった場合、そのフォルダが作成されます。

「timeout」コマンド

「timeout」コマンドは、指定した秒数、コマンド プロセッサを一時停止します。ここでは直前でフォルダ作成処理が入る可能性があるので、以降の処理を確実にするため3秒のウェイトをかけています。ウェイト処理構文「timeout /t 秒数 /nobreak > nul」の各オプションの説明は以下の通りです。

  • 「/t 秒数」:コマンド プロセッサが処理を続行する前に待機する秒数を指定
  • 「/nobreak」:キー入力を無視するよう指定
  • 「 > nul 」:コマンドのメッセージを「nul(空ファイル)」へリダイレクトして表示を抑制
  • ※nul :MS-DOSにおけるデバイスファイル名の一種、空ファイルを表し、データを読み書きしても何も起らない

「for」コマンドとループ内処理

「for」コマンドに「/f」オプションを付けた、ファイル読込み書式「for /d %%[アルファベット1文字] in ([ファイル名]) do ([コマンド])」を使用し「for /f %%a in (“%indir%\*.%targetext%”) do ~ 」と記述しました。「ファイル名」に、任意ディレクトリ(ここでは indir )配下への、対象の拡張子(ここでは targetext )ファイルのワイルドカード参照「“%indir%\*.%targetext%”」を指定しました。このワイルドカード参照が「xcopy」コマンドに渡されて展開され、任意ディレクトリ配下の「bat」ファイルをディレクトリ構造ごとコピーされる事になります。

「for」コマンド「/f」オプションの挙動

「for」コマンドの「/f」オプションは、用途が多岐にわたり複雑です。ここでは、「for /fオプション 変数名 in (ファイル名) do コマンド」で、ファイルを行単位で変数に読込む、一番シンプルな構文を使いました。「ファイル名」としてワイルドカード参照を代入した場合、渡されたコマンド内でうまく展開されるようです。

挙動確認の為「xcopy」の代わりに「dir」コマンドを使って以下のようなバッチを作成しました。検証用の参照先を、同じフォルダ下に設置した「get」フォルダ下(set indir=.\get)の「bat」ファイル(set targetext=bat)に設定します。

「for」ループ内で「dir」コマンドに、ワイルドカード参照(for /f %%a in (“%indir%\*.%targetext%”) do dir “%%a” /b /s)を渡します。※「dir」コマンドとオプションについてはバッチのコメントを参照ください。

@echo off
cd /d %~dp0
setlocal

rem 変数設定
set targetext=bat
set indir=.\get

rem for /fオプション 変数名 in (ファイル名) do コマンド
rem /fオプション:ファイルまたはコマンドの実行結果を行単位で変数に読込む
rem --------------------------------------------------------------------
rem dir         :ファイルとディレクトリを一覧表示
rem /bオプション:ファイル名またはディレクトリ名だけを表示する
rem /sオプション:サブディレクトリに含まれるファイルやディレクトリも含めて表示する
for /f %%a in ("%indir%\*.%targetext%") do dir "%%a" /b /s

endlocal
pause
@echo on

以上を「test02.bat」ファイルにまとめて、任意フォルダ(C:\temp\test\batch_dir)に置きます。同じ場所に、以下のフォルダ構造で、検証用の「get」フォルダを作成しました。

C:\TEMP\TEST\BATCH_DIR\GET
│  bat01.bat
│
└─sub01
    │  bat02.bat
    │
    └─sub02
            bat03.bat

このバッチをコマンドプロンプトから起動すると、以下のようなプロンプト画面が表示されます。ワイルドカード参照が、渡された先の「dir」コマンド内で正常に展開され、検証用の「get」フォルダ下の「bat」ファイルが漏れなく、フルパスで一覧表示される事が確認できます。

ループ内の「xcopy」コマンドの挙動

「xcopy」コマンドは、ファイルやディレクトリ構造のコピーを行います。ここでは、基本的な構文の、「xcopy [コピー元] [コピー先] [オプション]」を使用しています。「for」ループ内で「xcopy」コマンドの「コピー元」に、ワイルドカード参照(for /f %%a in (“%indir%\*.%targetext%”) do xcopy “%%a” “%outdir%\” /d /i /s /y)を渡され「xcopy」内で展開、付加したオプション設定に従って「コピー先(”%outdir%\”)」にコピーされます。※オプションについては、バッチのコメントを参照ください。

rem for /fオプション 変数名 in (ファイル名) do コマンド
rem /fオプション:ファイルまたはコマンドの実行結果を行単位で変数に読込む
rem --------------------------------------------------------------------
rem xcopy       :ファイルをフォルダ構造ごとコピー
rem /dオプション:コピー先に同名のファイルが存在する場合に更新日が新しいファイルのみコピーする
rem /iオプション:コピー先のディレクトリが存在しない場合は新規にディレクトリを作成する
rem /sオプション:ファイルが存在する場合のみディレクトリごとコピーする
rem /yオプション:同名のファイルが存在する場合、 上書きの確認を行わない
for /f %%a in ("%indir%\*.%targetext%") do xcopy "%%a" "%outdir%\" /d /i /s /y

動作確認

テスト環境の事前準備

「for」コマンド「/f」オプションの挙動確認で使用した検証環境を利用し「get_bat_files.bat」ファイルを、任意フォルダ(C:\temp\test\batch_dir)に置きます。

またテスト用に、テスト①~③環境の「get_bat_files.bat」ファイルは、以下の変数設定とします。

rem 変数設定
set targetext=bat
set indir=.\get
set outdir=.\out

テスト①環境

同じ場所の検証用「get」フォルダは、フォルダ構造も含めて流用し、全フォルダ下に対象ファイル(ここでは、batファイル)があるパターンとして、テスト①環境としました。

テスト②環境

次に、一部ファイルが対象外ファイル(ここでは、txtファイル)のパターンとして「get」フォルダ下のフォルダ構造を以下の様に設定し、テスト②環境としました。

C:\TEMP\TEST\BATCH_DIR\GET
│  bat01.bat
│
└─sub01
    │  bat02.txt ←ここだけtxtファイル
    │
    └─sub02
            bat03.bat

テスト③環境

次に、全フォルダが空フォルダのパターンとして「get」フォルダ下のフォルダ構造を以下の様に設定し、テスト③環境としました。

C:\TEMP\TEST\BATCH_DIR\GET
└─sub01
    └─sub02

テスト④環境

また、当初の使用目的に耐えられるか検証する為、参照先を、Dドライブ(set indir=D:\)配下に設定を戻して、テスト④環境とし動作確認を行いました。

rem 変数設定
set targetext=bat
set indir=D:\
set outdir=.\out

テスト

テスト①~④の各テスト環境を整えた後、任意フォルダ(C:\temp\test\batch_dir)に置いた「get_bat_files.bat」ファイルを、ダブルクリックで起動して動作検証を行いました。

テスト①結果

「get_bat_files.bat」ファイルを起動後、コマンドプロンプトに以下の通り、コピーされたファイルの一覧が表示されます。

「get_bat_files.bat」ファイルと同じフォルダに「out」フォルダが作成されることを確認します。

作成された「out」フォルダ下のフォルダ構造は以下の通りで、取得対象の「bat」ファイルが、コピー元の「get」フォルダ下のフォルダ構造ごとコピーされている事が確認できました。

C:\TEMP\TEST\BATCH_DIR\OUT
│  bat01.bat
│
└─sub01
    │  bat02.bat
    │
    └─sub02
            bat03.bat

テスト②結果

続けて「get」フォルダ下をテスト②環境とし「out」フォルダ下を削除しました。「get_bat_files.bat」ファイルを起動後、コマンドプロンプトに以下の通り、コピーされたファイルの一覧が表示され、取得対象外の「txt」ファイルが除外される事が確認できました。

「out」フォルダ下にコピーされたフォルダ構造は以下の通りで、取得対象のファイルのみフォルダ構造ごとコピーされる事が確認できました。

C:\TEMP\TEST\BATCH_DIR\OUT
│  bat01.bat
│
└─sub01
    └─sub02
            bat03.bat

テスト③結果

続けて「get」フォルダ下をテスト③環境とし「out」フォルダ下を削除しました。「get_bat_files.bat」ファイルを起動後、コマンドプロンプトに以下の通り、取得対象のファイルが無い場合、一つもコピーされない事が確認できました。

「out」フォルダ下にコピーされたフォルダ構造は以下の通りで、取得対象のファイルが無い場合、何もコピーされない事が確認できました。

C:\TEMP\TEST\BATCH_DIR\OUT
サブフォルダーは存在しません

テスト④結果

続けて「out」フォルダごと削除し「get_bat_files.bat」ファイルの変数設定をテスト④環境に変更しました。バッチファイル起動後、コマンドプロンプトに以下の通り、Dドライブ配下の「bat」ファイルが検出され、フルパスで一覧表示される事が確認できました。

作成された「out」フォルダ下のフォルダ構造は以下の通りで、取得対象の「bat」ファイルが、コマンドプロンプトに一覧表示された、フルパスのフォルダ構造ごとコピーされる事が、確認できました。

C:\TEMP\TEST\BATCH_DIR\OUT
├─Android
│  └─android-sdk
│      ├─build-tools
│      │  └─30.0.2
│      │          apksigner.bat
│      │          d8.bat
│      │          dx.bat
│      │          mainDexClasses.bat
│      │
│      └─tools
│          │  android.bat
│          │  monitor.bat
以下、省略

まとめ

今回はバッチ化したかった要件が、殆ど「xcopy」コマンドの基本機能と設定するオプションの選定で実現でき「xcopy」コマンドの高機能ぶりを再認識できました。バッチファイル作成の過程で、「 %~dp0 」や「if not exist ~」等、バッチ処理でよく使われる件を、学び直すことが出来たので、良い勉強になりました。

参考文献

コメント

タイトルとURLをコピーしました