以下は、カレントディレクトリにあるtxtファイルについて、その内容を表示するというバッチファイルです(エラー判定でERRORLEVELを遅延展開させたいので、setlocal enabledelayedexpansionを呼出しています)。
setlocal enabledelayedexpansion
for %%a in (*.txt) do (
type "%%a" >nul 2>&1
if !ERRORLEVEL! neq 0 (
echo "[%%a] Error has occurred."
) else (
echo "[%%a] is OK."
)
)
endlocal
SetLocal EnableDelayedExpansionの罠
上記のバッチファイルは作り的に考慮漏れです。なぜなら、カレントディレクトリに「!xxx!.txt」のような名前のファイルが存在するとエラーが発生するからです。
>check.bat
"[.txt] Error has occurred."
エラー発生時に%%aに代入されていたのは!xxx!.txtという文字列ですが、!xxx!の部分が遅延展開と認識されてしまうようです。
つまり、%%aが!xxx!.txtに展開され、さらに!xxx!を展開しようとします。しかしxxxという変数は未定義であるため、!xxx!の展開結果はブランクとなります。そのなれの果てが.txtという文字列です。
パスワードやファイル名など、含まれる文字をコントロールできない変数を扱う場面では、事実上setlocal enabledelayedexpansionが使えなくなってしまいます。
では、どんな回避方法があるでしょうか。
回避方法
以下のように、SetLocal~EnableDelayedExpansionのスコープを最小限にしたり
for %%a in (*.txt) do (
type "%%a" >nul 2>&1
setlocal enabledelayedexpansion
if !ERRORLEVEL! neq 0 (
endlocal
echo "[%%a] Error has occurred."
) else (
endlocal
echo "[%%a] is OK."
)
)
forブロック内の処理を関数に逃すというテもあります。こうすればSetLocal EnableDelayedExpansionを使う必要がなくなります。
for %%a in (*.txt) do (
call :func "%%a"
)
exit /b 0
:func
type "%~1" >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo "[%~1] Error has occurred."
) else (
echo "[%~1] is OK."
)
exit /b 0
if ERRORLEVEL Nを使うのもよいです。
for %%a in (*.txt) do (
type "%%a" >nul 2>&1
if ERRORLEVEL 1 (
echo "[%%a] Error has occurred."
) else if ERRORLEVEL 0 (
echo "[%%a] is OK."
) else (
echo "[%%a] Error has occurred."
)
)
というわけですので、バッチファイル全体をSetLocal EnableDelayedExpansionで囲むなどという恐ろしいことをしてはなりません…。
余談
なお、値を関数の引数に渡すときに以下のように値をダブルクォートしているのは「ファイル名に空白文字が含まれたファイル」対策です。ダブルクォテーションが無いと、空白文字で区切られた複数の引数として認識されてしまうためです。
call :func "%%a"
コメント