以下は、カレントディレクトリにある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"