バッチファイルでファイル・フォルダをバックアップする（日付文字列付きコピー）

Y子です。
ファイルやフォルダをバックアップするバッチファイルを作ってみました。

作業中ファイルの世代管理を、手作業でこんな感じにすること、ありませんか？

* 作業中のWordファイル「`仕様書.docx`」を「`仕様書_20210428.docx`」にコピー
* ファイル群を格納したフォルダ「`Project`」を「`Project_20210428`」にコピー

これを、更新日をもとに自動でやってくれるバッチファイルを作りました。
_【[確認環境：Windows 10 Home (Bld. 19042.928)](https://qiita.com/koiwaiwaiko/items/ef2d457ae1e1a7c8b698 "Windows10のバージョンを確認・表記する")】_

# 仕様
* ファイルやフォルダをコピーする。
* コピー先は、コピー元と同じフォルダとする。
* コピー先ファイル名・フォルダ名は、 __コピー元の更新日付__ と __連番__ を拡張子前に付けて、「`filename_yyyymmdd (1).docx`」「`dirname_yyyymmdd (1)`」等とする。
* コピー先ファイル名・フォルダ名が既に存在している場合、連番をインクリメントして、存在しない名前を探す。
* 複数の引数に対応する。10個以上にも対応する。

# 使い方
* バッチファイルに、ファイル名やフォルダ名を引数で与えることで動作する。
* バッチファイルに、ファイルやフォルダをマウスでドロップしてもよい。
* バッチファイルのショートカットを「`sendto`」に置き、右クリック「送る」から選択してもよい。
* 上記を、複数ファイル同時に行ってもよい。

# コード
```bat:backup_with_datestr.bat
@echo off
setlocal

rem ▼複数引数ループ開始
:label_top

rem 引数がない場合は最後に飛んで終了する
if "%~1"=="" (goto :label_end)

rem 対象ファイル・フォルダ「C:\work dir\test.txt」「C:\work dir\dir」
set str_file=%~1

rem 上位フォルダのフルパス「C:\work dir\」
set str_dir=%~dp1

if not exist "%str_file%" (
  rem このファイルはないのでshiftして次へ
  echo ファイルが存在しません："%str_file%"
  goto :label_bottom
)

rem ファイル名分割…拡張子なし「test」と拡張子のみ「.txt」
set str_new_fn=%~n1
set str_new_ex=%~x1

rem ファイルの更新日時「2021/04/02 09:46」形式→「20210402」
set str_date=%~t1
set str_date=%str_date:~0,4%%str_date:~5,2%%str_date:~8,2%

rem カウンタ初期化
set cnt=1

rem ▽ファイル名決定ループ開始
:label_newfile
rem 新しいファイル名(仮)「test_yyyymmdd (n).txt」
set str_newfile=%str_new_fn%_%str_date% (%cnt%)%str_new_ex%

rem 同名のファイルがある場合は、次のファイル名を探す「test_yyyymmdd (n+1).txt」
if exist "%str_dir%%str_newfile%" (
  set /a cnt+=1
  goto :label_newfile
)
rem △ファイル名決定ループ終了

rem コピーする：フォルダの場合
if exist "%str_file%\" (
  echo d | xcopy /s /e /h /k /q "%str_file%" "%str_dir%%str_newfile%"
rem コピーする：ファイルの場合
) else (
  echo f | xcopy /r /q "%str_file%" "%str_dir%%str_newfile%"
)
echo コピーしました：%str_newfile%

:label_bottom
rem 引数をシフトし、最初に戻る
shift
goto :label_top
rem ▲複数引数ループ終了

:label_end
echo 処理を終了しました
endlocal
pause
```

# 説明
コピーのコマンドは`xcopy`を使いました。
`xcopy`は、コピー先がファイルかフォルダかを聞いてくるため、手入力が必要になりますが、フォルダは「`echo d`」、ファイルは「`echo f`」をパイプで入力することで、手入力を不要としています。

オプションの意味は以下の通りです。
結果オーライで使っているので、不要なものもあるかもしれません…。
__フォルダコピー時：`xcopy /s /e /h /k /q`__
　`/s`　サブフォルダもコピーする
　`/e`　空フォルダもコピーする
　`/h`　隠しファイルもコピーする
　`/k`　読み取り専用属性もコピーする
　`/q`　コピー中のファイル名表示を抑制する
__ファイルコピー時：`xcopy /r /q`__
　`/r`　読み取り専用ファイルも上書きする
　`/q`　コピー中のファイル名表示を抑制する

# 使い方・実行結果
まず、`C:\work dir\test01.txt`というファイルを、バッチファイルにドロップしてみます。
4/28の日付と連番1がついて、`C:\work dir\test01_20210428 (1).txt`ができました。

```:実行結果
C:\work dir\test01_20210428 (1).txt は受け側のファイル名ですか、
またはディレクトリ名ですか
(F= ファイル、D= ディレクトリ)? f
1 個のファイルをコピーしました
コピーしました：test01_20210428 (1).txt
処理を終了しました
続行するには何かキーを押してください . . .
```

続けて、`C:\work dir\dir01`というフォルダを、バッチファイルにドロップしてみます。
フォルダ`dir01_20210414 (1)`ができました。

```:実行結果
C:\work dir\dir01_20210414 (1) は受け側のファイル名ですか、
またはディレクトリ名ですか
(F= ファイル、D= ディレクトリ)? d
21 個のファイルをコピーしました
コピーしました：dir01_20210414 (1)
処理を終了しました
続行するには何かキーを押してください . . .
```

さらに続けて、ファイルとフォルダを混ぜて、`dir01`・`test01.txt`・`test02.txt`をバッチファイルにドロップしてみます。
見にくいので改善の余地がありそうですが、`dir01_20210414 (2)`・`test01_20210428 (2).txt`・`test02_20210403 (1).txt`ができました。既に存在する名前を避けて通番が振られれています。

```:実行結果
C:\work dir\dir01_20210414 (2) は受け側のファイル名ですか、
またはディレクトリ名ですか
(F= ファイル、D= ディレクトリ)? d
21 個のファイルをコピーしました
コピーしました：dir01_20210414 (2)
C:\work dir\test01_20210428 (2).txt は受け側のファイル名ですか、
またはディレクトリ名ですか
(F= ファイル、D= ディレクトリ)? f
1 個のファイルをコピーしました
コピーしました：test01_20210428 (2).txt
C:\work dir\test02_20210403 (1).txt は受け側のファイル名ですか、
またはディレクトリ名ですか
(F= ファイル、D= ディレクトリ)? f
1 個のファイルをコピーしました
コピーしました：test02_20210403 (1).txt
処理を終了しました
続行するには何かキーを押してください . . .
```

# 参考
「`echo d`」等の使い方を教えていただきました。

http://piyopiyocs.blog115.fc2.com/blog-entry-278.html

# おわりに
更新日付の取得は __[こちら](https://qiita.com/koiwaiwaiko/items/2715251fce0c5f529429 "バッチファイルにドロップして、ファイル名に更新日時を付加する")__ 、10個以上の引数を扱うのは __[こちら](https://qiita.com/koiwaiwaiko/items/f47e9cd25f61a82435d4 "バッチファイルで10個以上の引数を扱う")__ 、使われていない連番の探索は __[こちら](https://qiita.com/koiwaiwaiko/items/5a87f04da7790e2be9ad "バッチファイルでファイルを退避する（ファイル名末尾に「(1)」を付加する）")__ で検討したロジックを、それぞれ使いました。
道具が増えて、いろんなことができるようになってきました。

「`(1)`の時は連番不要」という仕様もアリかなと思いますが、コードが長くなるので今回は採用しませんでした。
またフォルダの日付は、フォルダの変更日でなく、含まれるファイルのうち一番新しい日付を取得した方が、バックアップの意味としてはより正しそうですが、ちょっと難しそうなので、今回はファイルと同じ処理で良しとしました。

では！