ファイル／フォルダ名の大文字・小文字＆全角・半角文字を変換するバッチファイルをささっと作る（完結編）

# 課題

前回そして前々回とファイル／フォルダ名を変換するバッチファイルを作成した記事を投稿した。

- [ファイル／フォルダ名の大文字・小文字を変換するバッチファイルをささっと作る - Qiita](https://qiita.com/TETSURO1999/items/c88e1c3d078079b7f385)
- [ファイル／フォルダ名の全角・半角文字を変換するバッチファイルをささっと作る - Qiita](https://qiita.com/TETSURO1999/items/ebefa21b4a752ccf541b)

人の欲望とは恐ろしいもので，これまで手作業でチマチマとファイル名を変えていたのがバッチファイルによって一発変更できるようになるといろいろと欲（改善要求）が出てくるのだ。その中でも最大の意見は<font color=red><b>「ファイル（またはフォルダ）を再帰的に検索できるようにしたい」</b></font>というものである。

# 方法その１

別に新たなツールを作らなくても良い。Windows には `ForFiles` コマンドというコマンドがあり，それを使えば十分用が足せるのだ。具体的なコマンドライン指定は以下のようになる。なおユーザの環境によっては `ucase.cmd` は絶対パスで与える必要があるかもしれない。

```:ForFiles コマンドを用いる場合
forfiles /S /P 検索フォルダ /M 検索パターン /C "cmd /c ucase.cmd @path"
```

ただ，実際に試してみると，この `ForFiles` コマンドはクッソ遅いのだ。

:::note
なぜこんなに遅いのか原因は不明だが，非常に高機能であることから，内部的に何らかの中間言語を生成してインタプリター実行しているのではないかと推察している。
:::

# 方法その２

特定のディレクトリ以下のファイル／フォルダを再帰的に検索したい。この場合，一番高速かつ手軽な方法は `dir` コマンドの出力を利用することである。検索パスにはワイルドカードが含まれてもよいし，複数のパスを羅列することもできる。フォルダを対象とするときはオプション `/ad` を付ける必要があり，逆にファイルのみを対象にする（フォルダを対象にしない）ときはオプション `/a-d` を付ける。

```:ファイルを対象とする場合
for /F "usebackq tokens=*" %%I in ( `dir /s /b /a-d 検索パス ...` ) do call :CONVERT "%%~I"
```

```:フォルダを対象とする場合
for /F "usebackq tokens=*" %%I in ( `dir /s /b /ad 検索パス ...` ) do call :CONVERT "%%~I"
```

なお，検索パスにマッチしなかった場合「ファイルが見つかりません」というエラーメッセージが出力されるので，それが気に入らない人は標準エラー出力を `NUL` デバイスにリダイレクトすればよい。

実装コードを以下に示す。

<details><summary><b>UCASE2.CMD のソースコードハコチラ</b></summary>

```:UCASE2.CMD
@echo off
rem ------------------------------------------------------------------------
rem ファイル名（複数可）を大文字に変換する
rem ------------------------------------------------------------------------ 
setlocal enabledelayedexpansion
set ROPT=0
set DOPT=0
set QOPT=0
set VOPT=0
set COUNT=0
rem ------------------------------------------------------------------------
rem オプション解析
rem ------------------------------------------------------------------------
set ARGS=
if "%~1"=="" goto USAGE
:LOOP
  set OPT=%~1
  if /I "%OPT%"=="/R" set ROPT=1&&goto ENDIF
  if /I "%OPT%"=="/D" set DOPT=1&&goto ENDIF
  if /I "%OPT%"=="/Q" set QOPT=1&&goto ENDIF
  if /I "%OPT%"=="/V" set VOPT=1&&goto ENDIF
  if "%OPT:~0,1%"=="/" goto ERROR
  if defined ARGS (
    set ARGS=%ARGS% "%~1"
  ) else (
    set ARGS="%~1"
  )
  :ENDIF
  shift
  if "%~1"=="" goto BREAK
goto LOOP
:ERROR
echo オプション %OPT% には対応していません！
exit /b
:BREAK
if not defined ARGS (
  echo ファイル名を指定して下さい！
  exit /b
)
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
set DIROPT=/b
if "%DOPT%"=="1" (
  set DIROPT=%DIROPT% /ad
) else (
  set DIROPT=%DIROPT% /a-d
)
if "%ROPT%"=="1" (
  set DIROPT=/s %DIROPT%
)
for /F "usebackq tokens=*" %%I in ( `dir %DIROPT% %ARGS% 2^>NUL` ) do (
  call :CONVERT "%%~I"
)
if "%VOPT%"=="1" (
  echo %COUNT% 個のファイル名を変更します。
) else (
  echo %COUNT% 個のファイル名を変更しました。
)
exit /b
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
:CONVERT
set OLDNAME=%~nx1
set NEWNAME=%OLDNAME%
for %%C in ( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ) do (
  set "NEWNAME=!NEWNAME:%%C=%%C!"
)
if "%OLDNAME%"=="%NEWNAME%" exit /b
if "%QOPT%"=="0" echo %~1	%NEWNAME%
if "%VOPT%"=="0" ren "%~1" "%NEWNAME%"
set /a COUNT=COUNT+1
exit /b
rem ------------------------------------------------------------------------
rem ヘルプメッセージ
rem ------------------------------------------------------------------------
:USAGE
echo ファイル名（複数可）を大文字に変更します。
echo.
echo UCASE2(.CMD) [/R] [/D] [/Q] [/V] [ドライブ:][パス][ファイル名]
echo.
echo /R  ファイル／フォルダを再帰的に検索します。
echo /D  フォルダ名を変更します。
echo /Q  変更したファイル名を表示しません。
echo /V  変更対象となるファイル名を表示するだけで、変更は行いません。
exit /b
```

</details>

ただし，上記の `UCASE2.CMD` を深い階層を持つフォルダに適用すると，ファイル名の変更を開始するまで暫く待たされ，もっさりとした起動感になる。これは，Windows の場合，`dir` コマンドが検索結果をいったんテンポラリファイルに出力し終えてから `for /F` コマンドがこのテンポラリファイルを読み出すことにって処理が開始されるからだ。このあたり，パイプ処理が並列に実行される UNIX と異なり，Windows のまだまだ不便なところだと思う。このあたり PowerShell では改善されてはいるようだが。

# 方法その３

瞬時に動作を開始させたいのであれば `for /R` コマンドを使ってファイルを再帰的に検索すれば良い。

```:ファイルを対象とする場合
for /R 検索フォルダ %%I in ( 検索パターン ) do call :CONVERT "%%~I"
```

```:フォルダを対象とする場合
for /R 検索フォルダ /D %%I in ( 検索パターン ) do call :CONVERT "%%~I"
```

ところが，方法その２と同様に検索パスのみを与えて，スクリプト側で検索フォルダと検索パターンに分離させようとすると，この方法は面倒な課題が続出する。

### パスの分離
`for /R` コマンドには検索フォルダと検索パターンの二つの引数が必要であり，たとえば `c:\sample\*.doc` というパスが与えられていたとして，これをフォルダ名 `c:\sample\` とベース名 `*.doc` に分割する必要がある。たとえば変数 `%I ` に `c:\sample\*.txt` というパスが与えられているとき，`%~dpI` は `c:\sample\` となるが，`%~nxI` は `*.txt` とはならず，ワイルドカードが展開されて `*.txt` にマッチングするファイルの一つに変換されてしまうのだ。マッチするファイルが存在しない場合は空文字列になる。

### ワイルドカード文字の変換
これを防ぐためにはパス名のワイルドカード文字 `*` をいったん文字列 `__WILDCARD__` に変換し，フォルダ名とベース名に分割した後に `__WILDCARD__` を `*` に戻せばよい。バッチファイルにおいては `set` コマンドによる文字列置換機能が知られているが，実はワイルドカード文字 `*` を置換することができない。何故かエスケープ文字 `^` も効かない。※詳しくは参考文献参照のこと。

```:文字列置換機能
set VAR=%VAR:文字列1=文字列2%
```

このため下記のように一文字ずつ置換していくしかない。

```:ワイルドカード文字の置換
rem ------------------------------------------------------------------------
rem ワイルドカード文字の置換
rem * → __ASTERISK__
rem ? → __QUESTION__
rem ------------------------------------------------------------------------
:WILDCARD
setlocal
set STR=%~1
set RET=
:WILDCARD_LOOP
  if "%STR%"=="" goto WILDCARD_BREAK
  set CHR=%STR:~0,1%
  if "%CHR%"=="*" set CHR=__ASTERISK__
  if "%CHR%"=="?" set CHR=__QUESTION__
  set RET=%RET%%CHR%
  set STR=%STR:~1%
  goto WILDCARD_LOOP
:WILDCARD_BREAK
endlocal&&set %2=%RET%
exit /b
```

ちなみにワイルドカード文字 `?` のほうは `set` コマンドで問題なく置換できるため，上記のサブルーチンを使用しなくても良い。

### for /R コマンドの課題

`for /R` コマンドで与える検索パターン文字列にワイルドカード文字 `*` および `?` が含まれている場合，検索パターン文字列にマッチするファイル名をリストアップしてくれる。このときリストアップしたファイルは必ず存在する。ところが，ワイルドカード文字が含まれていない場合，単に与えた検索パターン文字列を羅列するだけである。この場合，リストアップされたファイルが実在する保証はない。これを防ぐためには `if exist` コマンドを用いてファイルの実在チェックを行えば良いが，実在チェックだけではファイルとフォルダの区別がつかないのでファイルの属性を得て判断せざるを得ない。

```:検索対象がファイルの場合
for /R 検索フォルダ %I in ( 検索パターン ) do (
  set ATTR=%%~aI
  if "!ATTR:~0,1!"=="-" call :CONVERT "%%~I"
)
```

```:検索対象がフォルダの場合
for /R 検索フォルダ /D %I in ( 検索パターン ) do (
  set ATTR=%%~aI
  if "!ATTR:~0,1!"=="d" call :CONVERT "%%~I"
)
```

### 実装コード

ということで上記のテクニックを組み合わせた実装コードを以下に示す。

<details><summary><b>UCASE3.CMD のソースコードはコチラ</b></summary>

```:UCASE3.CMD
@echo off
setlocal enabledelayedexpansion
rem ------------------------------------------------------------------------
rem ファイル名（複数可）を大文字に変換する
rem ------------------------------------------------------------------------ 
set ROPT=0
set DOPT=0
set QOPT=0
set VOPT=0
set COUNT=0
rem ------------------------------------------------------------------------
rem オプション解析
rem ------------------------------------------------------------------------
set ARGS=
if "%~1"=="" goto USAGE
:LOOP
  set OPT=%~1
  if /I "%OPT%"=="/R" set ROPT=1&&goto ENDIF
  if /I "%OPT%"=="/D" set DOPT=1&&goto ENDIF
  if /I "%OPT%"=="/Q" set QOPT=1&&goto ENDIF
  if /I "%OPT%"=="/V" set VOPT=1&&goto ENDIF
  if "%OPT:~0,1%"=="/" goto ERROR
  call :WILDCARD "%~1" STR
  if defined ARGS (
    set ARGS=%ARGS% "%STR%"
  ) else (
    set ARGS="%STR%"
  )
  :ENDIF
  shift
  if "%~1"=="" goto BREAK
goto LOOP
:ERROR
echo オプション %OPT% には対応していません！
exit /b
:BREAK
call :MAIN %ARGS%
exit /b
rem ------------------------------------------------------------------------
rem ワイルドカードの置換
rem * → __ASTERISK__
rem ? → __QUESTION__
rem ------------------------------------------------------------------------
:WILDCARD
set STR=%~1
set RET=
:WILDCARD_LOOP
  if "%STR%"=="" goto WILDCARD_BREAK
  set C=%STR:~0,1%
  if "%C%"=="*" set C=__ASTERISK__
  if "%C%"=="?" set C=__QUESTION__
  set RET=%RET%%C%
  set STR=%STR:~1%
  goto WILDCARD_LOOP
:WILDCARD_BREAK
set %2=%RET%
exit /b
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
:MAIN
:MAIN_LOOP
  if "%ROPT%"=="1" (
    set RFLAG=/R %~dp1
    set PATTERN=%~nx1
  ) else (
    set RFLAG=
    set PATTERN=%~1
  )
  set PATTERN=%PATTERN:__ASTERISK__=*%
  set PATTERN=%PATTERN:__QUESTION__=?%
  if "%DOPT%"=="1" (
    for %RFLAG% /D %%I in ( %PATTERN% ) do (
      set ATTR=%%~aI
      if "!ATTR:~0,1!"=="d" call :CONVERT "%%~I"
    )
  ) else (
    for %RFLAG% %%I in ( %PATTERN% ) do (
      set ATTR=%%~aI
      if "!ATTR:~0,1!"=="-" call :CONVERT "%%~I"
    )
  )
  shift
  if "%~1"=="" goto MAIN_BREAK
  goto :MAIN_LOOP
:MAIN_BREAK
if "%VOPT%"=="1" (
  echo %COUNT% 個のファイル名を変更します。
) else (
  echo %COUNT% 個のファイル名を変更しました。
)
exit /b
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
:CONVERT
set OLDNAME=%~nx1
set NEWNAME=%OLDNAME%
for %%C in ( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ) do (
  set "NEWNAME=!NEWNAME:%%C=%%C!"
)
if "%OLDNAME%"=="%NEWNAME%" exit /b
if "%QOPT%"=="0" echo %~1	%NEWNAME%
if "%VOPT%"=="0" ren "%~1" "%NEWNAME%"
set /a COUNT=COUNT+1
exit /b
rem ------------------------------------------------------------------------
rem ヘルプメッセージ
rem ------------------------------------------------------------------------
:USAGE
echo ファイル名（複数可）を大文字に変更します。
echo.
echo UCASE3(.CMD) [/R] [/D] [/Q] [/V] [ドライブ:][パス][ファイル名] ...
echo.
echo /R  ファイル／フォルダを再帰的に検索します。
echo /D  フォルダ名を変更します。
echo /Q  変更したファイル名を表示しません。
echo /V  変更対象となるファイル名を表示するだけで、変更は行いません。
exit /b
```

</details>

# 実行結果の比較

とあるフォルダ以下にある合計 4672 個のファイル名の変更を行った際の実行時間の比較である。`ForFiles` コマンドはすごく遅いことが分かり，今後，大量のファイルを扱うときは躊躇してしまう結果となった。圧倒的に最速なのは `dir` コマンドの出力を用いる `UCASE2.CMD` である。初動はワンテンポ遅れるが，その後の動作は流れるように速い。苦労して作った `UCASE3.CMD` は初動こそレスポンス良いものの，その後はまったりと動き，意外に奮わない結果となった。

```mermaid
%%{
    init:{
        "xyChart":{
            "width":640,
            "height":400,
            "titleFontSize":16,
            "xAxis":{
                "titleFontSize":16,
                "labelFontSize":16,
                "axisLineWidth":1,
                "tickWidth":1,
                "showLabel":true
            },
            "yAxis":{
                "titleFontSize":16,
                "labelFontSize":16,
                "axisLineWidth":1,
                "tickWidth":1,
                "showLabel":true
            }
        },
        "themeVariables":{
            "fontFamily":"monospaced",
            "xyChart":{
                "plotColorPalette":"pink"
            }
        }
    }
}%%
xychart-beta
title "図１　実行時間の比較"
x-axis "" ["ForFiles + Ucase.cmd","Ucase2.cmd", "Ucase3.cmd"]
y-axis "実行時間［秒］" 0 --> 200
bar [189.48, 017.190, 84.850]
```

# おまけ

同様にして大文字を小文字に変換するバッチファイル `LCASE2.CMD` も簡単に作ることができる。サブルーチン `CONVERT` を差し替えるだけで良い。当然のことながら `dir` コマンドの出力を用いる `UCASE2.CMD` がベースである。

<details><summary><b>LCASE2.CMD のソースコードはコチラ</b></summary>

```:LCASE2.CMD
@echo off
rem ------------------------------------------------------------------------
rem ファイル名（複数可）を小文字に変換する
rem ------------------------------------------------------------------------ 
setlocal enabledelayedexpansion
set ROPT=0
set DOPT=0
set QOPT=0
set VOPT=0
set COUNT=0
rem ------------------------------------------------------------------------
rem オプション解析
rem ------------------------------------------------------------------------
set ARGS=
if "%~1"=="" goto USAGE
:LOOP
  set OPT=%~1
  if /I "%OPT%"=="/R" set ROPT=1&&goto ENDIF
  if /I "%OPT%"=="/D" set DOPT=1&&goto ENDIF
  if /I "%OPT%"=="/Q" set QOPT=1&&goto ENDIF
  if /I "%OPT%"=="/V" set VOPT=1&&goto ENDIF
  if "%OPT:~0,1%"=="/" goto ERROR
  if defined ARGS (
    set ARGS=%ARGS% "%~1"
  ) else (
    set ARGS="%~1"
  )
  :ENDIF
  shift
  if "%~1"=="" goto BREAK
goto LOOP
:ERROR
echo オプション %OPT% には対応していません！
exit /b
:BREAK
if not defined ARGS (
  echo ファイル名を指定して下さい！
  exit /b
)
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
set DIROPT=/b
if "%DOPT%"=="1" (
  set DIROPT=%DIROPT% /ad
) else (
  set DIROPT=%DIROPT% /a-d
)
if "%ROPT%"=="1" (
  set DIROPT=/s %DIROPT%
)
for /F "usebackq tokens=*" %%I in ( `dir %DIROPT% %ARGS% 2^>NUL` ) do (
  call :CONVERT "%%~I"
)
if "%VOPT%"=="1" (
  echo %COUNT% 個のファイル名を変更します。
) else (
  echo %COUNT% 個のファイル名を変更しました。
)
exit /b
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
:CONVERT
set OLDNAME=%~nx1
set NEWNAME=%OLDNAME%
for %%C in ( a b c d e f g h i j k l m n o p q r s t u v w x y z ) do (
  set "NEWNAME=!NEWNAME:%%C=%%C!"
)
if "%OLDNAME%"=="%NEWNAME%" exit /b
if "%QOPT%"=="0" echo %~1	%NEWNAME%
if "%VOPT%"=="0" ren "%~1" "%NEWNAME%"
set /a COUNT=COUNT+1
exit /b
rem ------------------------------------------------------------------------
rem ヘルプメッセージ
rem ------------------------------------------------------------------------
:USAGE
echo ファイル名（複数可）を小文字に変更します。
echo.
echo LCASE2(.CMD) [/R] [/D] [/Q] [/V] [ドライブ:][パス][ファイル名]
echo.
echo /R  ファイル／フォルダを再帰的に検索します。
echo /D  フォルダ名を変更します。
echo /Q  変更したファイル名を表示しません。
echo /V  変更対象となるファイル名を表示するだけで、変更は行いません。
exit /b
```

</details>

同様にして半角カタカナを全角カタカナに変換する `ZCASE2.CMD` および全角英数字・全角スペースを半角文字に変換する `HCASE2.CMD` もサブルーチン `CONVERT` を差し替えるだけで簡単に作ることができる。ただし，再帰検索を行うオプション `/R` とフォルダ名を変更するオプション `/D` は同時に指定できないようにした。これは再帰検索中にフォルダ名が変えられてしまうと不都合を生じるからだ。逆に Windows のファイルシステムは大文字・小文字を区別しないため，大文字・小文字を変換するときはオプション `/R` とオプション `/D` が両立する。

<details><summary><b>ZCASE2.CMD のソースコードはコチラ</b></summary>

```:ZCASE2.CMD
@echo off
rem ------------------------------------------------------------------------
rem ファイル名（複数可）の半角カタカナを全角カタカナに変換する
rem ------------------------------------------------------------------------ 
setlocal enabledelayedexpansion
set ROPT=0
set DOPT=0
set QOPT=0
set VOPT=0
set COUNT=0
rem ------------------------------------------------------------------------
rem オプション解析
rem ------------------------------------------------------------------------
set ARGS=
if "%~1"=="" goto USAGE
:LOOP
  set OPT=%~1
  if /I "%OPT%"=="/R" set ROPT=1&&goto ENDIF
  if /I "%OPT%"=="/D" set DOPT=1&&goto ENDIF
  if /I "%OPT%"=="/Q" set QOPT=1&&goto ENDIF
  if /I "%OPT%"=="/V" set VOPT=1&&goto ENDIF
  if "%OPT:~0,1%"=="/" goto ERROR
  if defined ARGS (
    set ARGS=%ARGS% "%~1"
  ) else (
    set ARGS="%~1"
  )
  :ENDIF
  shift
  if "%~1"=="" goto BREAK
goto LOOP
:ERRR
echo オプション %OPT% には対応していません！
exit /b
:BREAK
if not defined ARGS (
  echo ファイル名を指定して下さい！
  exit /b
)
if "%ROPT%%DOPT%"=="11" (
  オプション /R とオプション /D は同時に指定できません！
  exit /b
)
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
set DIROPT=/b
if "%DOPT%"=="1" (
  set DIROPT=%DIROPT% /ad
) else (
  set DIROPT=%DIROPT% /a-d
)
if "%ROPT%"=="1" (
  set DIROPT=/s %DIROPT%
)
for /F "usebackq tokens=*" %%I in ( `dir %DIROPT% %ARGS% 2^>NUL` ) do (
  call :CONVERT "%%~I"
)
if "%VOPT%"=="1" (
  echo %COUNT% 個のファイル名を変更します。
) else (
  echo %COUNT% 個のファイル名を変更しました。
)
exit /b
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
:CONVERT
set OLDNAME=%~nx1
set NEWNAME=%OLDNAME%
set NEWNAME=%NEWNAME:ｶﾞ=ガ%
set NEWNAME=%NEWNAME:ｷﾞ=ギ%
set NEWNAME=%NEWNAME:ｸﾞ=グ%
set NEWNAME=%NEWNAME:ｹﾞ=ゲ%
set NEWNAME=%NEWNAME:ｺﾞ=ゴ%
set NEWNAME=%NEWNAME:ｻﾞ=ザ%
set NEWNAME=%NEWNAME:ｼﾞ=ジ%
set NEWNAME=%NEWNAME:ｽﾞ=ズ%
set NEWNAME=%NEWNAME:ｾﾞ=ゼ%
set NEWNAME=%NEWNAME:ｿﾞ=ゾ%
set NEWNAME=%NEWNAME:ﾀﾞ=ダ%
set NEWNAME=%NEWNAME:ﾁﾞ=ヂ%
set NEWNAME=%NEWNAME:ﾂﾞ=ヅ%
set NEWNAME=%NEWNAME:ﾃﾞ=デ%
set NEWNAME=%NEWNAME:ﾄﾞ=ド%
set NEWNAME=%NEWNAME:ﾊﾞ=バ%
set NEWNAME=%NEWNAME:ﾋﾞ=ビ%
set NEWNAME=%NEWNAME:ﾌﾞ=ブ%
set NEWNAME=%NEWNAME:ﾍﾞ=ベ%
set NEWNAME=%NEWNAME:ﾎﾞ=ボ%
set NEWNAME=%NEWNAME:ﾊﾟ=パ%
set NEWNAME=%NEWNAME:ﾋﾟ=ピ%
set NEWNAME=%NEWNAME:ﾌﾟ=プ%
set NEWNAME=%NEWNAME:ﾍﾟ=ペ%
set NEWNAME=%NEWNAME:ﾎﾟ=ポ%
set NEWNAME=%NEWNAME:｡=。%
set NEWNAME=%NEWNAME:｢=「%
set NEWNAME=%NEWNAME:｣=」%
set NEWNAME=%NEWNAME:､=、%
set NEWNAME=%NEWNAME:･=・%
set NEWNAME=%NEWNAME:ｦ=ヲ%
set NEWNAME=%NEWNAME:ｧ=ァ%
set NEWNAME=%NEWNAME:ｨ=ィ%
set NEWNAME=%NEWNAME:ｩ=ゥ%
set NEWNAME=%NEWNAME:ｪ=ェ%
set NEWNAME=%NEWNAME:ｫ=ォ%
set NEWNAME=%NEWNAME:ｬ=ャ%
set NEWNAME=%NEWNAME:ｭ=ュ%
set NEWNAME=%NEWNAME:ｮ=ョ%
set NEWNAME=%NEWNAME:ｯ=ッ%
set NEWNAME=%NEWNAME:ｰ=ー%
set NEWNAME=%NEWNAME:ｱ=ア%
set NEWNAME=%NEWNAME:ｲ=イ%
set NEWNAME=%NEWNAME:ｳ=ウ%
set NEWNAME=%NEWNAME:ｴ=エ%
set NEWNAME=%NEWNAME:ｵ=オ%
set NEWNAME=%NEWNAME:ｶ=カ%
set NEWNAME=%NEWNAME:ｷ=キ%
set NEWNAME=%NEWNAME:ｸ=ク%
set NEWNAME=%NEWNAME:ｹ=ケ%
set NEWNAME=%NEWNAME:ｺ=コ%
set NEWNAME=%NEWNAME:ｻ=サ%
set NEWNAME=%NEWNAME:ｼ=シ%
set NEWNAME=%NEWNAME:ｽ=ス%
set NEWNAME=%NEWNAME:ｾ=セ%
set NEWNAME=%NEWNAME:ｿ=ソ%
set NEWNAME=%NEWNAME:ﾀ=タ%
set NEWNAME=%NEWNAME:ﾁ=チ%
set NEWNAME=%NEWNAME:ﾂ=ツ%
set NEWNAME=%NEWNAME:ﾃ=テ%
set NEWNAME=%NEWNAME:ﾄ=ト%
set NEWNAME=%NEWNAME:ﾅ=ナ%
set NEWNAME=%NEWNAME:ﾆ=ニ%
set NEWNAME=%NEWNAME:ﾇ=ヌ%
set NEWNAME=%NEWNAME:ﾈ=ネ%
set NEWNAME=%NEWNAME:ﾉ=ノ%
set NEWNAME=%NEWNAME:ﾊ=ハ%
set NEWNAME=%NEWNAME:ﾋ=ヒ%
set NEWNAME=%NEWNAME:ﾌ=フ%
set NEWNAME=%NEWNAME:ﾍ=ヘ%
set NEWNAME=%NEWNAME:ﾎ=ホ%
set NEWNAME=%NEWNAME:ﾏ=マ%
set NEWNAME=%NEWNAME:ﾐ=ミ%
set NEWNAME=%NEWNAME:ﾑ=ム%
set NEWNAME=%NEWNAME:ﾒ=メ%
set NEWNAME=%NEWNAME:ﾓ=モ%
set NEWNAME=%NEWNAME:ﾔ=ヤ%
set NEWNAME=%NEWNAME:ﾕ=ユ%
set NEWNAME=%NEWNAME:ﾖ=ヨ%
set NEWNAME=%NEWNAME:ﾗ=ラ%
set NEWNAME=%NEWNAME:ﾘ=リ%
set NEWNAME=%NEWNAME:ﾙ=ル%
set NEWNAME=%NEWNAME:ﾚ=レ%
set NEWNAME=%NEWNAME:ﾛ=ロ%
set NEWNAME=%NEWNAME:ﾜ=ワ%
set NEWNAME=%NEWNAME:ﾝ=ン%
if "%OLDNAME%"=="%NEWNAME%" exit /b
if "%QOPT%"=="0" echo %~1	%NEWNAME%
if "%VOPT%"=="0" ren "%~1" "%NEWNAME%"
exit /b
rem ------------------------------------------------------------------------
rem ヘルプメッセージ
rem ------------------------------------------------------------------------
:USAGE
echo ファイル名（複数可）の半角カタカナを全角カタカナに変換します。
echo.
echo ZCASE2(.CMD) [/R] [/D] [/Q] [/V] [ドライブ:][パス][ファイル名]
echo.
echo /R  ファイル／フォルダを再帰的に検索します。
echo /D  フォルダ名を変更します。
echo /Q  変更したファイル名を表示しません。
echo /V  変更対象となるファイル名を表示するだけで、変更は行いません。
exit /b
```

</details>

<details><summary><b>HCASE2.CMD のソースコードはコチラ</b></summary>

```:HCASE2.CMD
@echo off
rem ------------------------------------------------------------------------
rem ファイル名（複数可）の全角英数字・全角スペースを半角文字に変換する
rem ------------------------------------------------------------------------ 
setlocal enabledelayedexpansion
set ROPT=0
set DOPT=0
set QOPT=0
set VOPT=0
set COUNT=0
rem ------------------------------------------------------------------------
rem オプション解析
rem ------------------------------------------------------------------------
set ARGS=
if "%~1"=="" goto USAGE
:LOOP
  set OPT=%~1
  if /I "%OPT%"=="/R" set ROPT=1&&goto ENDIF
  if /I "%OPT%"=="/D" set DOPT=1&&goto ENDIF
  if /I "%OPT%"=="/Q" set QOPT=1&&goto ENDIF
  if /I "%OPT%"=="/V" set VOPT=1&&goto ENDIF
  if "%OPT:~0,1%"=="/" goto ERROR
  if defined ARGS (
    set ARGS=%ARGS% "%~1"
  ) else (
    set ARGS="%~1"
  )
  :ENDIF
  shift
  if "%~1"=="" goto BREAK
goto LOOP
:ERROR
echo オプション %OPT% には対応していません！
exit /b
:BREAK
if not defined ARGS (
  echo ファイル名を指定して下さい！
  exit /b
)
if "%ROPT%%DOPT%"=="11" (
  オプション /R とオプション /D は同時に指定できません！
  exit /b
)
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
set DIROPT=/b
if "%DOPT%"=="1" (
  set DIROPT=%DIROPT% /ad
) else (
  set DIROPT=%DIROPT% /a-d
)
if "%ROPT%"=="1" (
  set DIROPT=/s %DIROPT%
)
for /F "usebackq tokens=*" %%I in ( `dir %DIROPT% %ARGS% 2^>NUL` ) do (
  call :CONVERT "%%~I"
)
if "%VOPT%"=="1" (
  echo %COUNT% 個のファイル名を変更します。
) else (
  echo %COUNT% 個のファイル名を変更しました。
)
exit /b
rem ------------------------------------------------------------------------
rem ファイル名／フォルダ名を変更する
rem ------------------------------------------------------------------------
:CONVERT
set OLDNAME=%~nx1
set NEWNAME=%OLDNAME%
set NEWNAME=%NEWNAME:　= %
set NEWNAME=%NEWNAME:０=0%
set NEWNAME=%NEWNAME:１=1%
set NEWNAME=%NEWNAME:２=2%
set NEWNAME=%NEWNAME:３=3%
set NEWNAME=%NEWNAME:４=4%
set NEWNAME=%NEWNAME:５=5%
set NEWNAME=%NEWNAME:６=6%
set NEWNAME=%NEWNAME:７=7%
set NEWNAME=%NEWNAME:８=8%
set NEWNAME=%NEWNAME:９=9%
set NEWNAME=%NEWNAME:Ａ=A%
set NEWNAME=%NEWNAME:Ｂ=B%
set NEWNAME=%NEWNAME:Ｃ=C%
set NEWNAME=%NEWNAME:Ｄ=D%
set NEWNAME=%NEWNAME:Ｅ=E%
set NEWNAME=%NEWNAME:Ｆ=F%
set NEWNAME=%NEWNAME:Ｇ=G%
set NEWNAME=%NEWNAME:Ｈ=H%
set NEWNAME=%NEWNAME:Ｉ=I%
set NEWNAME=%NEWNAME:Ｊ=J%
set NEWNAME=%NEWNAME:Ｋ=K%
set NEWNAME=%NEWNAME:Ｌ=L%
set NEWNAME=%NEWNAME:Ｍ=M%
set NEWNAME=%NEWNAME:Ｎ=N%
set NEWNAME=%NEWNAME:Ｏ=O%
set NEWNAME=%NEWNAME:Ｐ=P%
set NEWNAME=%NEWNAME:Ｑ=Q%
set NEWNAME=%NEWNAME:Ｒ=R%
set NEWNAME=%NEWNAME:Ｓ=S%
set NEWNAME=%NEWNAME:Ｔ=T%
set NEWNAME=%NEWNAME:Ｕ=U%
set NEWNAME=%NEWNAME:Ｖ=V%
set NEWNAME=%NEWNAME:Ｗ=W%
set NEWNAME=%NEWNAME:Ｘ=X%
set NEWNAME=%NEWNAME:Ｙ=Y%
set NEWNAME=%NEWNAME:Ｚ=Z%
set NEWNAME=%NEWNAME:ａ=a%
set NEWNAME=%NEWNAME:ｂ=b%
set NEWNAME=%NEWNAME:ｃ=c%
set NEWNAME=%NEWNAME:ｄ=d%
set NEWNAME=%NEWNAME:ｅ=e%
set NEWNAME=%NEWNAME:ｆ=f%
set NEWNAME=%NEWNAME:ｇ=g%
set NEWNAME=%NEWNAME:ｈ=h%
set NEWNAME=%NEWNAME:ｉ=i%
set NEWNAME=%NEWNAME:ｊ=j%
set NEWNAME=%NEWNAME:ｋ=k%
set NEWNAME=%NEWNAME:ｌ=l%
set NEWNAME=%NEWNAME:ｍ=m%
set NEWNAME=%NEWNAME:ｎ=n%
set NEWNAME=%NEWNAME:ｏ=o%
set NEWNAME=%NEWNAME:ｐ=p%
set NEWNAME=%NEWNAME:ｑ=q%
set NEWNAME=%NEWNAME:ｒ=r%
set NEWNAME=%NEWNAME:ｓ=s%
set NEWNAME=%NEWNAME:ｔ=t%
set NEWNAME=%NEWNAME:ｕ=u%
set NEWNAME=%NEWNAME:ｖ=v%
set NEWNAME=%NEWNAME:ｗ=w%
set NEWNAME=%NEWNAME:ｘ=x%
set NEWNAME=%NEWNAME:ｙ=y%
set NEWNAME=%NEWNAME:ｚ=z%
if "%OLDNAME%"=="%NEWNAME%" exit /b
if "%QOPT%"=="0" echo %~1	%NEWNAME%
if "%VOPT%"=="0" ren "%~1" "%NEWNAME%"
exit /b
rem ------------------------------------------------------------------------
rem ヘルプメッセージ
rem ------------------------------------------------------------------------
:USAGE
echo ファイル名（複数可）の全角英数字・全角スペースを半角文字に変換する
echo.
echo HCASE2(.CMD) [/R] [/D] [/Q] [/V] [ドライブ:][パス][ファイル名]
echo.
echo /R  ファイル／フォルダを再帰的に検索します。
echo /D  フォルダ名を変更します。
echo /Q  変更したファイル名を表示しません。
echo /V  変更対象となるファイル名を表示するだけで、変更は行いません。
exit /b
```

</details>


# 参考文献

残念ながらバッチファイルでアスタリスク文字 `*` を一括置換する方法は存在しない様子。

- [バッチで*（アスタリスク・ワイルドカード）を文字として扱うには？ - teratail](https://teratail.com/questions/197960)

下記文献を見る限り `ForFiles` コマンドは高機能なのだが，いろいろ癖（フォルダパスに日本語が含まれているとエラーになる等）があって大変そうだ。せめて実行スピードが速かったら頑張って使いこなそうとやる気が出るのだが。

- [便利なWindowsコマンド - Qiita](https://qiita.com/lusf/items/ee3ea76e5e88413651e2)
- [都度調べていたforfilesコマンドをもう一度整理する - Qiita](https://qiita.com/KScookie/items/32127cf938b051a74b2e)
- [【BAT】複数フォルダ内を拡張子で抽出してコピーや削除する方法 - Qiita](https://qiita.com/yaju/items/96c7f557d4e752dd98f5)
- [forfilesコマンドのフォルダパスに日本語を含むと、無効な引数またはオプションだと怒られる - Qiita](https://qiita.com/KScookie/items/065e8259b7def1d8711e)
- [WindowsのForfilesコマンドで、特定のファイル名以外を操作する方法 - Qiita](https://qiita.com/motuo/items/18aab37e85d3012c1b8e)
- [【DOS】使える時に使いたいバッチ(bat)のコード - Qiita](https://qiita.com/Tabito/items/c18cdf33cf82a617bf0e)
- [windowsの指定日付より前のファイルを削除する - Qiita](https://qiita.com/gitya107/items/a11deb3d83d8d624175c)