遅延展開される CMD バッチスクリプトで ! を記述する方法
Windows のコマンドプロンプト CMD において、環境変数に遅延展開というものがあることを知ってる人も多いと思う。
CMD は、コマンドを1ステートメントづつ実行するため、IF 文や FOR 文の中で環境変数を扱うと、嬉しくないことが起こる。たとえば、
SET a=1 SET b=1 IF "%a%"=="1" ( SET b=2 ECHO a=%a%, b=%b% ……(x) ) ELSE ( ECHO a=%a%, b=%b% ) ECHO a=%a%, b=%b%
このようなバッチスクリプトを実行すると、
1: SET a=1 2: SET b=1 3: IF "1"=="1" ( : SET b=2 : ECHO a=1, b=1 ……(x) : ) ELSE ( : ECHO a=1, b=1 : ) 4: ECHO a=1, b=2
というステートメントに展開される。このことを知らない人は (x) の部分で b=2 となることを期待するかもしれないが、環境変数はステートメント単位で展開されてから実行される都合で、上記のような状態になる。SET b=2 が実行されないわけではないので、次のステートメントである4行目では、%b% は 2 に展開される。
一般的なプログラム言語の変数のように、環境変数を展開するためには遅延展開を用いる。
SETLOCAL ENABLEDELAYEDEXPANSION SET a=1 SET b=1 IF "%a%"=="1" ( SET b=2 ECHO a=!a!, b=!b! ……(x) ) ELSE ( ECHO a=!a!, b=!b! ) ECHO a=%a%, b=%b%
遅延展開は、% のかわりに ! で囲まれた環境変数名を、実行単位で評価してくれる。さて、バッチスクリプト中で % を表記するためには、%% とかけばよく、| や > のような入出力に関する制御文字は ^ を添えて ^> などと記載することができる。では、遅延展開が有効な場合に ! を記載するにはどうすればいいだろうか?
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION ECHO !! → ECHO は <ON> です ECHO ^! → ECHO は <ON> です
何が起こっているのだろうか?
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION ECHO !!foo!! → ECHO は <ON> です ECHO ^!bar^! → ECHO は <ON> です
もう少し試してみる。
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION SET foo=test ECHO !!foo!! → test ECHO ^!foo^! → test ECHO foo!!bar → foobar ECHO foo^!bar → foobar ECHO !!foo!!bar → testbar ECHO foo^!bar^! → foo
環境変数の遅延展開では、余った ! は捨てられるということがわかる。google で検索すると英語圏の MVP の方が ^! で回避できるようなことを書いているが、実際は上記の通りである。しかし、例示されているサンプルや文章を見ると「文字列中では」というような記載がある。
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION SET foo=test ECHO "foo^!bar" → "foo!bar" ECHO "^!foo^!bar^!" → "!foo!bar!"
見事な結果。用途としては、7-Zip で差分圧縮を実行するためであったので、クオートが ! の外側につくのは使いにくい。というわけで、拡張機能 (ENABLEEXTENSIONS のほうで有効になる)を利用して、
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION FOR /F %%F IN ( backup.cfg ) DO ( SET BASE_FILE=%%F_full.7z SET DIFF_FILE="^!%%F_diff.7z" 7z u !BASE_FILE! -u- -up0q3x2z0!DIFF_FILE:~! %%F 1>NUL 2>>err.log
みたいなかんじに ~ を使ってクオートを削除しました。その後で気がついたのですが、
ECHO ^^!
と、^ をエスケープして遅延展開前のステートメント解析のときに ^ が消えないようにする方法でも対応できました。このことから、コマンドラインの処理では、遅延展開が有効なときの ! は ^! と記載すればエスケープできることと、
という順序で実行されることがわかりました。文字列中で ^! を記載するというのは、^ によるエスケープが文字列中では必要ない(文字列中でリダイレクトを行ったりすることができない)ことを利用していたのであって、実際は c-1. の処理時点で ^! という表現が残っていればよかったということですね。