LoginSignup
2
1

More than 5 years have passed since last update.

Windowsで外部プログラムに任意の文字列をパラメータで渡す方法

Posted at

概要

たとえば、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独自のエスケープもあって、かなり難易度が高いです)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1