ProcessBuilder/Runtime.exec() 使用 Weka 命令行演示特殊行为

ProcessBuilder/Runtime.exec() with Weka Command Line Demonstrating Peculiar Behavior

下面基本上是我的完整问题的 MCVE,它更加混乱。您需要知道的是,直接在终端中输入以下行 运行s:

java -classpath /path/to/weka.jar weka.filters.MultiFilter \
    -F "weka.filters.unsupervised.attribute.ClusterMembership -I first" \
    -i /path/to/in.arff

这相对简单。基本上,我所做的就是尝试使用 ClusterMembership 过滤器的所有默认设置对来自 in.arff 的数据进行聚类,但我想忽略第一个属性。我在那里有 MultiFilter,因为在我的实际项目中,还有其他过滤器,所以我需要保留它。如前所述,这工作正常。但是,当我尝试 运行 与 ProcessBuilder 相同的行时,我得到一个 "quote parse error",并且嵌套引号的整个结构似乎崩溃了。证明这一点的一种方法是尝试使以下内容起作用:

List<String> args = new ArrayList<String>();
args.add("java");
args.add("-cp"); 
args.add("/path/to/weka.jar");
args.add("weka.filters.MultiFilter");
args.add("-F");
args.add("\"weka.filters.unsupervised.attribute.ClusterMembership"); 
args.add("-I"); 
args.add("first\"");
args.add("-i"); 
args.add("/path/to/in.arff");
ProcessBuilder pb = new ProcessBuiler(args);

// ... Run the process below

乍一看,您可能认为这与上面的行相同(这当然是我天真的想法)。事实上,如果我只是打印 args 并在每个字符串之间留有空格,那么生成的字符串是相同的,如果直接复制并粘贴到终端,则 运行 是完美的。但是,无论出于何种原因,该程序都无法运行,因为我收到消息(来自 Weka)Quote parse error。我尝试使用谷歌搜索并找到了关于 ProcessBuilder/Runtime.exec() 如何工作的 this question about how ProcessBuilder adds extra quotes to the command line (this led me to try numerous combinations of escape sequences, all of which did not work), and read this 文章(我尝试了 ProcessBuilder 和 Runtime.exec(),最终同样的问题仍然存在),但找不到任何与我需要的相关的东西。 Weka 已经有糟糕的文档,然后他们的 Wikispace 页面在几周前由于 Wikispaces 关闭而关闭,所以我在 Weka 方面发现的信息很少。

那么我的问题是:有没有办法得到类似我在 运行 上面提到的第二个示例这样的东西,这样我就可以将参数组合在一起以获得更大的命令?我知道它可能需要一些时髦的转义序列(或者可能不需要?),或者可能是我没有考虑过的其他东西。非常感谢这里的任何帮助。

编辑:我更新了问题,希望能更深入地了解我的问题所在。

您不需要将参数组合在一起。正如您已经指出的那样,它甚至不起作用。看看当我这样调用 Java 程序时会发生什么:

java -jar Test.jar -i -s "-t 500"

这是我的 "program":

public class Test {
  public static void main(String[] args) {
    for( String arg : args ) {
      System.out.println(arg);
    }      
  }
}

这是输出:

-i
-s
-t 500

参数中不包含引号,它们用于分组参数。因此,当您像以前那样将参数传递给 ProcessBuilder 时,本质上就像您在命令行上用引号编写它们一样,它们被视为单个参数,这会使解析器感到困惑。

仅当您有嵌套组件时才需要引号,例如FilteredClassifier。也许 my answer on another Weka question 可以帮助您处理那些嵌套组件。 (我最近将指向他们 wiki 的链接更改为指向 Google 缓存,直到他们建立新的 wiki。)

由于您没有具体说明是什么情况导致您考虑分组,您可以尝试为 Weka 获取一个可用的命令行,然后将其用作像我这样的程序的输入。然后你可以看到你需要如何将它们传递给 ProcessBuilder.

对于您的示例,我猜以下内容会起作用:

List<String> args = new ArrayList<String>();
args.add("java");
args.add("-cp"); 
args.add("/path/to/weka.jar");
args.add("weka.filters.MultiFilter");
args.add("-F");
args.add("weka.filters.unsupervised.attribute.ClusterMembership -I first");
args.add("-i"); 
args.add("/path/to/in.arff");
ProcessBuilder pb = new ProcessBuiler(args);

其他详细信息

Weka内部发生的事情基本上如下:参数中的选项首先由weka.filters.Filter处理,然后所有非通用过滤器选项由weka.filters.MultiFilter处理,其中包含以下代码在 setOptions(...):

filters = new Vector<Filter>();
while ((tmpStr = Utils.getOption("F", options)).length() != 0) {
    options2 = Utils.splitOptions(tmpStr);
    filter = options2[0];
    options2[0] = "";
    filters.add((Filter) Utils.forName(Filter.class, filter, options2));
}

这里,tmpStr-F选项的值,将被Utils.splitOption(tmpStr)source code)处理。在那里,所有引用和取消引用的魔法都发生了,因此下一个组件将收到一个选项数组,看起来就像它是第一级组件一样。