ProcessBuilder与Runtime.exec()的区别

9
我正在尝试使用Java创建一个前端应用程序,使用Inkscape的命令行功能处理批量SVG转换。我正在从https://sourceforge.net/projects/conversionsvg/获取并更新代码。原始开发人员使用Runtime.getRuntime().exec(String)调用Inkscape。我遇到的问题是在使用方法A和方法B之间存在一些不一致性。我创建了一个简单的Java测试项目来演示执行的不同操作。

CallerTest.java

package conversion;

import java.io.IOException;

public class CallerTest {
  
    static String pathToInkscape = "\"C:\\Program Files\\Inkscape\\inkscape.exe\"";  
    
    public static void main(String[] args) {
      
      ProcessBuilderCaller processBuilder = new ProcessBuilderCaller();
      RuntimeExecCaller runtimeExec = new RuntimeExecCaller();
      
      // methodA() uses one long command line string
      try {
        
        String oneLongString_ProcessBuilder = pathToInkscape + " -f \"C:\\test.svg\" -D -w 100 -h 100 -e \"C:\\ProcessBuilder-methodB.png\"";
        String oneLongString_RuntimeExec =    pathToInkscape + " -f \"C:\\test.svg\" -D -w 100 -h 100 -e \"C:\\RuntimeExec-methodA.png\"";
        
//        processBuilder.methodA(oneLongString_ProcessBuilder);
        runtimeExec.methodA(oneLongString_RuntimeExec);
        
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      
      // methodB() uses an array containing the command and the options to pass to the command
      try {

        String[] commandAndOptions_ProcessBuilder = {pathToInkscape, " -f \"C:/test.svg\" -D -w 100 -h 100 -e \"C:\\ProcessBuilder-methodB.png\""};
        String[] commandAndOptions_RuntimeExec =    {pathToInkscape, " -f \"C:/test.svg\" -D -w 100 -h 100 -e \"C:\\RuntimeExec-methodB.png\""};
        
        processBuilder.methodB(commandAndOptions_ProcessBuilder);
//        runtimeExec.methodB(commandAndOptions_RuntimeExec);
        
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
}

RuntimeExecCaller.java

package conversion;

import java.io.IOException;

public class RuntimeExecCaller {
    Process process;
    
    // use one string
    public void methodA(String oneLongString) throws IOException {
      process = Runtime.getRuntime().exec(oneLongString);
    }
    
    // use the array
    public void methodB(String[] commandAndOptions) throws IOException {
      process = Runtime.getRuntime().exec(commandAndOptions);
    }
}

ProcessBuilderCaller.java

package conversion;

import java.io.IOException;

public class ProcessBuilderCaller {
    Process process;
    
    // use one string
    public void methodA(String oneLongString) throws IOException {
      process = new ProcessBuilder(oneLongString).start();
    }
    
    // use the array
    public void methodB(String[] commandAndOptions) throws IOException {
      process = new ProcessBuilder(commandAndOptions).start();
    }
}

结果

两个methodA(String)调用都有效,但在调用methodB(String[])时,Inkscape启动并且参数传递不正确。在执行methodB(String[])后,我会得到每个错误对话框的Inkscape错误信息:

无法加载请求的文件 -f C:/test.svg -D -w 100 -h 100 -e C:\RuntimeExec-methodB.png

无法加载请求的文件 -f C:/test.svg -D -w 100 -h 100 -e C:\ProcessBuilder-methodB.png

当我点击对话框上的关闭按钮时,Inkscape弹出一个新的空白文档。所以,我有几个问题:

Runtime.getRuntime().exec(String)Runtime.getRuntime().exec(String[])之间有什么区别?

JavaDoc说Runtime.exec(String)调用Runtime.exec(command, null)(即Runtime.exec(String cmd, String[] envp)),然后调用Runtime.exec(cmdarray, envp)(即Runtime.exec(String[] cmdarray, String[] envp))。因此,如果Runtime.getRuntime().exec(String)已经调用了Runtime.exec(String[]),为什么使用不同的方法会得到不同的结果?

是否有什么事情正在幕后发生,Java根据调用的方法设置环境不同?

1个回答

12

我怀疑你的问题源于你指定参数列表的方式。实际上,你将 "-f C:/test.svg -D -w 100 -h 100 -e C:\RuntimeExec-methodB.png" 作为一个单独的参数传递给了Inkscape。

你需要做的是逐个传递参数,像这样:

String[] commandAndOptions_ProcessBuilder = {pathToInkscape, "-f", "C:\\est.svg", "-D", "-w", "100", "-h", "100", "-e", "C:\\ProcessBuilder-methodB.png"};
String[] commandAndOptions_RuntimeExec = {pathToInkscape, "-f", "C:\\test.svg", "-D", "-w", "100", "-h", "100", "-e","C:\\RuntimeExec-methodB.png"};

大致地说,当你使用Runtime.exec(String)时,你传入的值会被Shell解析并分解成参数列表。而当你使用Runtime.exec(String[])时,你直接提供了参数列表,因此不需要进行处理。这样做的好处是你无需为特殊的Shell字符进行转义,因为这些参数不会被Shell所解析。


我非常确定Java可以在ProcessBuilderRuntime.exec()中处理参数的拆分,但除此之外是正确的。 - Jonathan
2
@Jonathan,快速查看Runtime可以发现你是正确的——StringTokenizer用于在空格上拆分字符串。这意味着如果您的可执行文件路径中有空格,则需要使用Runtime.exec(String[])。 - userkci

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接