概要
たとえば、Javaで外部プログラムを起動するには
ProcessBuilder processBuilder = new ProcessBuilder("MyProg.exe", "param1", "param2");
Process process = processBuilder.start();
とすれば Windows 以外では問題なく起動できます。 ですが、Windows の場合、パラメーターに空白が含まれていたり「"」が含まれていたりすると思ったとおりに動作しません。
この問題は、Java だけでなく C, Perl なんかでも同じです。
(Cygwin だと、そこで閉じている限り大丈夫っぽい。C# はそもそも、パラメータが1つしかない。でも、main に渡される引数は複数個なんですが...)
とりあえず、ここでは、とにかく、
- あるプログラムから、べつのプログラムにコマンドラインパラメータで文字列を渡したい
- 渡す元プログラム上の文字列と、先のプログラム上の文字列が、とにかく一致していてほしい
というケースを考えます。
各言語(起動する側)ごとの対処方法
Java
public static Process runProcess(String... cmdArray) {
ArrayList<String> cmdList = new ArrayList<String>();
for (String cmd : cmdArray) {
cmdList.add(transForWinCmd(cmd));
}
ProcessBuilder processBuilder = new ProcessBuilder(cmdList);
return processBuilder.start();
}
private static String transForWinCmd(String arg) {
// 末尾の \ のエスケープ (必須)
arg = arg.replaceAll("(\\\\+)(\"|$)", "$1$1$2");
// " を \" に変換 (必須)
arg = arg.replaceAll("\"", "\\\\\"");
// 対象プロセスが Java7, 8 の場合はこれも
// (他の場合、いらないが、あっても問題はなさそう)
arg = arg.replaceAll("(\\\\+)([?*]+)(\\\\)", "$1$1\"$2\"$3");
arg = arg.replaceAll("(\\\\+)([?*]+)(\\\\)", "$1$1\"$2\"$3");
// " で囲って終わり
return "\"" + arg + "\"";
}
Perl
Perl では、system 関数に、(コマンド, param1, ..., paramN) のように配列で渡せば OK という仕様だけど、Windows では完璧でないので、以下のようにする。
use Encode;
sub systemEx {
my @args = map {
local $_ = $_;
# 末尾の \ のエスケープ (必須)
s/(\\+)("|$)/$1$1$2/g;
# " を \" に変換 (必須)
s/"/\\"/g;
# 対象プロセスが Java7, 8 の場合はこれも
s/(\\+)([?*]+)(\\)/$1$1"$2"$3/g;
s/(\\+)([?*]+)(\\)/$1$1"$2"$3/g;
# SJIS に変換 (日本語環境の場合はこれが必要)
if (Encode::is_utf8($_)) { $_ = Encode::encode("MS932", $_); }
# " で囲む
"\"$_\"";
} @_;
system(@args);
}
どこに問題があるのか?
OS のバグ? のように感じますが、Windows のプロセス起動 API である CreateProcess は、プロセスおよびそのパラメータを示すものは、文字列の配列ではなく、文字列1つだけです。
Linux のように、パラメータが複数あるインターフェースはアプリケーション側ということになりますが、大抵、コンパイラー付属の標準ライブラリーをみんな使います。
ということで、問題は、標準ライブラリーにあると思われます。
Cの標準ライブラリにプロセスを起動する関数がありますが、この関数の実装が不十分なんじゃないかと思います。
(Java も Perl も、C言語で実装されているので、同じ問題が発生するということです)
ちなみに、1つのコマンドライン文字列を分離する規則は
https://msdn.microsoft.com/ja-jp/library/a1y7w461.aspx
にある通り、複雑ですが、ちゃんとした規則が公開されています。
その他...
起動対象が Ruby の場合や mingw で作られたものの場合は、別の対処が必要です。
あと、Windows の bat プログラムに渡す場合も...
(ここで書いた対処に加え、bat独自のエスケープもあって、かなり難易度が高いです)