ファイル・フォルダの差分更新をするバッチファイル

OKWaveや他の質問サイトなどで「バッチファイルでファイル・フォルダの差分コピーをしたい」という質問がされると、大抵がフリーソフトを紹介という斜め上の回答で〆られてしまっています。微妙。

んじゃあバッチファイル(OS標準コマンド)では不可能なの?死ぬの?
というわけでやってみました。可能でした。

−−−−−−−−ここから−−−−−−−−
@echo off
title 差分更新コピー
setlocal enabledelayedexpansion
set COPYBY=%~1
set COPYTO=%~2

xcopy "%COPYBY%\*.*" "%COPYTO%\*.*" /D /E /I /V /Y
dir "%COPYTO%\" /A -D /B /S>TEMP.TXT
set LINE=%COPYTO%
call :COUNT
for /F "tokens=* delims=&" %%i in (TEMP.TXT) do call :SUB "%%i"
del TEMP.TXT
EXIT

:SUB
set LINE=%~1
if not exist "%COPYBY%!LINE:~%NUM%!" del "%LINE%"
goto :EOF

:COUNT
set NUM=
:COUNTLOOP
set /A NUM=%NUM%+1
set LINE=%LINE:~1%
if not "%LINE%"=="" goto :COUNTLOOP
goto :EOF
−−−−−−−−ここまで−−−−−−−−

■つかいかた
「このバッチファイル.bat "更新元のパス" "更新先のパス"」
他のバッチから呼び出す場合はcallではなくstartを使ってください。

■かいせつ
動作は2フェーズにわかれています。
フェーズ1:更新元にある、更新先にあるものより日付が新しいファイルをサブフォルダごとコピーする(これはxcopyの標準機能で可能)
フェーズ2:更新先にあるが更新元にないファイルを、更新先から削除する

フェーズ2の動作ですがさらに細かく解説すると、
1.更新先にあるファイル一覧(全ファイルのフルパス)をtxtファイルに出力
2.「更新先として入力された文字列」の文字数をカウント
3.更新先にあるファイルのフルパスの先頭から「更新先として入力された文字列」の文字数分を差し引いて、頭に「更新元として指定された文字列」をくっつける(配下のフォルダ構造〜ファイル名は共通で、パスの頭の部分だけ更新元のものに挿げ替えた状態)。
4.3のファイルが存在するかどうかを確認し、(更新元に)ファイルが存在しない場合は、更新先にあるそのファイルを削除する。
という段階を踏んでいます。

たぶんここでキモになるのが変数の展開方法で、「ファイルのフルパスxの先頭からn文字を削除する」ということをやろうとすると、xにnを入れ込んだ二重構造の変数が必要になります。

以下に
set WORD=ABCDEFG
とした場合の例を挙げて説明すると、

例1:環境変数WORDの内容を表示する ⇒ABCDEFG
echo %WORD%

例2:環境変数WORDの3文字目以降の内容を表示する ⇒DEFG
echo %WORD:~3%
という風になるけれど、

例3:環境変数WORDのn文字目以降の内容を表示する(?)
set NUM=3
echo %WORD:~%NUM%%
とすると3という数字が正しく展開されず ⇒ABCDEFGNUM%
という風に展開されてしまいます。

意図した動作をさせるには変数NUMを3へと先に展開し「%WORD:~3%」という風にしてから、その後に変数WORDを展開する必要があるわけですが、これはDOS窓に標準装備のコマンド拡張機能というやつで実現可能です。

・setlocal enabledelayedexpansionを実行すると変数の遅延展開が利用可能になる。
・%で囲む表記の場合は参照時すぐに変数が展開される
・!で囲む表記の場合はコマンド実行の直前に変数が展開される

上の例なら同じWORDでも%WORD%で呼び出すのと!WORD!で呼び出すのとでは、内部挙動が変わるということです。単体で使う分には呼び出される結果に違いはないけれど、展開のタイミングに差をつけることで他の変数と組み合わせることが可能になるわけです。

例4:
setlocal enabledelayedexpansion
set NUM=3
echo !WORD:~%NUM%!
とするとNUM、WORDという順に変数が展開され ⇒DEFG
という結果が出力されます。



追記:更新先がルートフォルダだった場合にエラー吐くみたいだったので少し修正しました。