†DOSとバッチとスクリプト†

ファイル名中の「!」の処理

作成開始日 2018.03.04
最終更新日 2020.12.22

環境変数の遅延展開を有効にすると、文字列中の「!」が機能文字と看做されて、文字列中から消えてしまう。たとえば、forループで「bigbang!2.mp4」というファイル名を取得しても、バッチ内では「!」のない「bigbang2.mp4」というファイル名と解釈されてしまう。もちろん、「bigbang2.mp4」というファイル名で「bigbang!2.mp4」にアクセスすることはできない。

このような場合、遅延展開を有効にする前に、ファイル名中の「!」を「_」等に置換してリネームしておくと良い。そのために、以下のような処理を考えた。

【注意】「ren *!.* *_.*」や「ren ???!.* ???_.*」などのワイルドカードを使ったリネームは、ほとんどの場合、期待通りの動作をしないか、期待通りになったとしても汎用性がない。

for %%f in (*!*.*) do (
  set orig=%%f
  set newf=%orig:!=_%		←文字の置換処理(別項参照)
  ren "%orig%" "%newf%"
)
致って素直な処理だが、実はこれはまともに機能しない。%orig%も%newf%も期待したような値を取らない。すべて空白になったり、すべて最後のファイル名になったりする。有名な環境変数の展開の問題である(別項参照)。

もちろん、遅延環境変数展開を有効にすれば、このバッチは期待通りの動作をしてくれる。ただし、遅延展開を有効にすると「!」が扱えなくなる。これは「!」の問題を回避するための下処理なのだから、「!」が扱えないのでは意味が無い−−根本的なディレンマである。何とか、遅延展開なしで変数を変化させたい。

で、それを解決するのが;

for %%f in (*!*.*) do (
  set orig=%%f
  call :ext_ren		←サブルーチンのコール
)

exit /b			←明示的に終了(サブルーチンへの抜けの防止)

:ext_ren		←サブルーチン
  set newf=%orig:!=_%
  ren "%orig%" "%newf%"
exit /b
処理内容は全く同じで、ただ処理をサブルーチン化しただけだが、 恐らく、CALLされる度に新規にプロセスが作成され、変数が初期化されるのだろう。 これによって、遅延展開をしなくても変数の中身の変化を反映させることができる。

【参考】遅延展開する前に文字列中の「!」を「^!」に置換しておけば良いのではないかと思ったが、これも効果はなかった。たとえば、遅延展開前に「set st=aho^!」としておいても、遅延展開後に参照すると「aho!」ではなく「aho」になってしまう。

【追記】ファイル名から「!」を削除するだけなら、次のような方法もある。この問題のキモは《遅延展開をオンにすると「!」が扱えず、オフにするとループ内での文字列操作ができない》と言う点にある。以下の方法ではそれを逆手に取って、遅延展開自体を文字列操作(「!」の削除)に利用している。「echo %%f> temp.txt」は単にファイル名を書き出しているようにしか見えないが、遅延展開下ではこの段階で「!」が強制的に削除される。ただし、この方法では「!」の削除しかできない(他文字への置換不可)。環境変数の代わりに外部ファイルへ書き出さざるを得ないのもカッコ悪い(遅延展開オン時とオフ時の変数を同時に参照することはできない)。
for %%f in (*!*.*) do (

setlocal enabledelayedexpansion
echo %%f> temp.txt
endlocal

for /f "tokens=* delims=" %%a in (temp.txt) do (
ren "%%f" "%%a"
)

)
del temp.txt
もうちっと手間をかければ、置換処理も不可能ではない。これは、遅延展開オフでは不可能なループ内文字列処理の代わりに、for /fによるパターン分割を使うという発想。以下では「!」を「_」に置換している。ただし、置換処理が行えるのは、一つのファイル名内で1個所・1文字だけ。特に、複数箇所に出現されると困ったことになる(前記の方法では複数箇所でも対応可能…だと思う)。
for %%f in (*!*.*) do (

echo %%f> orig.txt
for /f "tokens=1-2 delims=!" %%a in (orig.txt) do (
echo %%a_%%b> new.txt
)

for /f "usebackq tokens=* delims=" %%a in (new.txt) do (
ren "%%f" "%%a"
)

)
del orig.txt
del new.txt


【DOSとバッチとスクリプト目次】 【ホーム】