(2010/04/03 20:43)
パス名にカンマやセミコロンを含むファイルをバッチファイルに送ろう(SendTo)として、いろいろ困ったことになりました。対策方法を考えたので、公開してみます。
# 2010/04/19 追記。ファイル名に全角空白が含まれる場合にも対応しました。
問題の背景。
コマンドプロンプトにおいて、空白文字というのはパラメータの区切り文字として認識されます。したがって、ファイル名(パス名)に空白文字を含むファイルを バッチファイル等に渡したい場合、ファイル名(パス名)を二重引用符で囲む必要があります。
以下は、動作検証のための簡単なバッチファイルです。
EchoArgs.BAT
@ECHO OFF
ECHO %%*=%*
:LOOP
IF "%~1" == "" GOTO END
ECHO %1
SHIFT
GOTO LOOP
:END
PAUSE
上記のバッチファイルは、渡されたコマンドライン引数をそれぞれ表示します。たとえば、
EchoArgs.BAT a b
などと実行すると、
%*=a b
a
b
と表示されます。「a b」というコマンドライン引数が、「a」と「b」の二つのパラメータとして認識されていることがわかります。
また、
EchoArgs.BAT "a b"
のように、二重引用符で囲んで渡してやれば、
%*="a b"
"a b"
前述のとおり、「a b」が ひとつのパラメータとして認識されます。
エクスプローラの中の人が気を利かせたこと。
次に、上記の EchoArgs.BAT をエクスプローラの「送る (SendTo)」に登録してみます。そして、「a」と「a b」いう名前のファイルを作成し(場所はどこでもいいですが、ここではCドライブの直下に作成)、それぞれ EchoArgs.BAT に送ってみます。
「a」というファイルを送った場合:
%*=C:\a
C:\a
「a b」というファイルを送った場合:
%*="C:\a b"
"C:\a b"
「a」と「a b」を両方選択して送った場合:
%*="C:\a b" C:\a
"C:\a b"
C:\a
これらの結果から、以下のことがわかります。
- エクスプローラから「送る」と、フルパスが渡される。
- 複数のファイルを送ることができ、その場合 複数のパスは空白文字で区切られる。
- 送られるファイルのパス内に空白文字が含まれる場合は、二重引用符で囲んで送られる。
したがって、バッチファイルはパス内に空白文字を含むファイルも正しく取り扱える。
問題発生。
冒頭で、コマンドプロンプトが 空白文字をパラメータの区切り文字として扱う という話をしましたが、この話には続きがあります。
どちらかというとあまり有名ではないのですが、コマンドプロンプトの野郎は、空白文字だけでなく、カンマ(,)やセミコロン(;)もパラメータの区切り文字として認識します。
したがって、パス名にカンマやセミコロンを含むファイルについて、空白文字を含む場合と同じ配慮(二重引用符で囲む)が必要になります。
# 2010/04/19 追記。コマンドプロンプト大先生は、全角空白もパラメータの区切り文字として認識してくださるようです。(XPとVistaで確認) このタコがッ!!
さて、ここで実験として、「c,d」「e;f」「g, h」(※カンマの後ろに空白文字あり) というファイルを作成し、EchoArgs.BAT に送ってみます。
「c,d」というファイルを送った場合:
%*=C:\c,d
C:\c
d
「e;f」というファイルを送った場合:
%*=C:\e;f
C:\e
f
「g, h」というファイルを送った場合:
%*="C:\g, h"
"C:\g, h"
これらの結果から、以下のことがわかります。
- エクスプローラは、パス内にカンマやセミコロン(、全角空白などのパラメータ区切り文字)が存在し、かつパス内に空白文字を含まないファイルを送る場合には、二重引用符で囲んでくれない。
- エクスプローラから上記のようなファイルを送られた場合、バッチファイルはうまく動作しない。
(単一のファイルを指すパス文字列を、複数のパラメータであると誤認する)
- 行き着く先は、「指定されたファイルが見つかりません。」
力ずくで何とかしてみた。
# 2010/04/19 全角空白を含むパスにも対応。
EchoArgs2.BAT
@ECHO OFF
ECHO %%*=%*
SET ARGS=%*
SET ARGS=%ARGS:,=$comma$%
SET ARGS=%ARGS:;=$semicolon$%
SET ARGS=%ARGS: =$fullwidthsp$%
CALL %~dp0EchoArgs2Main.BAT %ARGS%
EchoArgs2Main.BAT
@ECHO OFF
:LOOP
IF "%~1" == "" GOTO END
SET ARG1=%1
SET ARG1=%ARG1:$comma$=,%
SET ARG1=%ARG1:$semicolon$=;%
SET ARG1=%ARG1:$fullwidthsp$= %
ECHO %ARG1%
SHIFT
GOTO LOOP
:END
PAUSE
作戦の概要。
- バッチファイルを、二つに分ける。
- 一つ目のバッチファイルで、コマンドライン引数の全体をあらわす文字列(
%*)に対し、以下の置き換えを行う。
- 「
,」→「$comma$」
- 「
;」→「$semicolon$」
- 「
」(全角空白文字)→「$fullwidthsp$」
- 一つ目のバッチファイルで、上記の置き換え結果を引数として 二つ目のバッチファイルを起動する。
- 二つ目のバッチファイルで、個々のコマンドライン引数に対して、一つ目のバッチファイルでの置き換えを元に戻す。
- 二つ目のバッチファイルで、置き換えを元に戻した本来の引数に対して、処理を行う。
- (制限事項)パス名に「
$comma$」や「$semicolon$」などが含まれると破綻。
動作検証。
%*=C:\a
C:\a
%*="C:\a b"
"C:\a b"
%*="C:\a b" C:\a
"C:\a b"
C:\a
%*=C:\c,d
C:\c,d
%*=C:\e;f
C:\e;f
%*="C:\g, h"
"C:\g, h"
%*="C:\a b" C:\c,d C:\e;f
"C:\a b"
C:\c,d
C:\e;f
補足。
今回の話は、ファイル名そのものにカンマやセミコロンがある場合だけでなく、ファイルのパス名のどこかに含まれる場合全般についてのお話です。
# 2010/04/19 追記。老婆心ながら一言。上記のバッチファイルを参考にして何かを作成されることが万が一あるならば、EchoArgs2Main.BAT の「SET ARG1=%1」「ECHO %ARG1%」を、それぞれ「SET ARG1=%~1」「ECHO "%ARG1%"」と読み替えたほうが、実際の応用では有用だと思われます。