ProcessBuilder와 Runtime.exec ()의 차이점


96

Java 코드에서 외부 명령을 실행하려고하는데 Runtime.getRuntime().exec(...)와 사이에 차이점이 new ProcessBuilder(...).start()있습니다.

사용시 Runtime:

Process p = Runtime.getRuntime().exec(installation_path + 
                                       uninstall_path + 
                                       uninstall_command + 
                                       uninstall_arguments);
p.waitFor();

exitValue는 0이고 명령은 정상적으로 종료됩니다.

그러나 ProcessBuilder:

Process p = (new ProcessBuilder(installation_path +    
                                 uninstall_path +
                                 uninstall_command,
                                 uninstall_arguments)).start();
p.waitFor();

종료 값은 1001이고 명령 waitFor은 반환 되지만 중간에서 종료됩니다 .

문제를 해결하려면 어떻게해야 ProcessBuilder합니까?

답변:


99

의 다양한 오버로드는 Runtime.getRuntime().exec(...)문자열 배열 또는 단일 문자열을 사용합니다. 의 단일 문자열 오버로드 exec()는 문자열 배열을 사용하는 exec()오버로드 중 하나에 문자열 배열을 전달하기 전에 문자열을 인수 배열로 토큰 화합니다 . 반면 ProcessBuilder생성자는 문자열의 varargs 배열 또는 문자열의 a 만 취하며 List배열 또는 목록의 각 문자열은 개별 인수로 간주됩니다. 어느 쪽이든, 얻은 인수는 실행을 위해 OS에 전달되는 문자열로 결합됩니다.

예를 들어 Windows에서는

Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");

DoStuff.exe주어진 두 개의 인수 로 프로그램을 실행합니다 . 이 경우 명령 줄이 토큰 화되고 다시 합쳐집니다. 하나,

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

이름이있는 프로그램이있을 발생하지 않는 한, 실패 DoStuff.exe -arg1 -arg2에를 C:\. 토큰 화가 없기 때문입니다. 실행할 명령은 이미 토큰 화되었다고 가정합니다. 대신

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe", "-arg1", "-arg2");

또는 대안으로

List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);

여전히 작동하지 않습니다. List <String> params = java.util.Arrays.asList (installation_path + uninstall_path + uninstall_command, uninstall_arguments); 프로세스 qq = new ProcessBuilder (params) .start ();
gal

7
"installation_path + uninstall_path + uninstall_command"라는 문자열 연결이 의미가 있다고 믿을 수 없습니다.
Angel O'Sphere

8
Runtime.getRuntime (). exec (...)는 명령에서 명시 적으로 지정하지 않는 한 셸을 호출 하지 않습니다 . 이것은 최근 "Shellshock"버그 문제와 관련하여 좋은 점입니다. 이 대답은 cmd.exe 또는 이와 동등한 (즉, 유닉스의 경우 / bin / bash) 실행될 것임을 나타 내기 때문에 잘못된 것 같습니다. 대신 토큰 화는 Java 환경 내에서 수행됩니다.
Stefan Paul Noack

@ noah1989 : 피드백 주셔서 감사합니다. 나는 (희망적으로) 일을 명확히하고 특히 shells 또는 cmd.exe.
Luke Woodward

간부에 대한 파서 ... 알아 내기 위해 몇 일이 걸렸다있는 매개 변수화 된 버전 중 하나로서 매우 동일한 작동하지 않습니다
드류 델라 노

18

어떻게 봐 Runtime.getRuntime().exec()받는 문자열 명령을 전달합니다 ProcessBuilder. 그러므로, 호출하는 토크 나이저를 사용하여 개별 토큰으로 명령을 폭발 exec(String[] cmdarray, ......)를 구성한다 ProcessBuilder.

당신이를 구성하는 경우 ProcessBuilder대신 하나 하나의 문자열 배열로, 같은 결과를 얻을 수 있습니다.

ProcessBuilder생성자는 필요 String...하나의 문자열로 전체 명령을 전달하는 터미널에서 따옴표로 그 명령을 호출하는 것과 같은 효과가 있으므로, 가변 인자를 :

shell$ "command with args"

14

의 구현 은 다음 ProcessBuilder.start()Runtime.exec()같기 때문에 차이가 없습니다 Runtime.exec().

public Process exec(String command) throws IOException {
    return exec(command, null, null);
}

public Process exec(String command, String[] envp, File dir)
    throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");

    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

public Process exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}

그래서 코드 :

List<String> list = new ArrayList<>();
new StringTokenizer(command)
.asIterator()
.forEachRemaining(str -> list.add((String) str));
new ProcessBuilder(String[])list.toArray())
            .environment(envp)
            .directory(dir)
            .start();

다음과 같아야합니다.

Runtime.exec(command)

의견을 보내 주신 dave_thompson_085 에게 감사드립니다 .


2
그러나 Q는 그 방법을 호출하지 않습니다. 그것은 (간접적으로) 호출 public Process exec(String command, String[] envp, File dir)- String하지 String[]- 통화 StringTokenizer다음에 (간접적으로) 전달되는 배열의 풋 토큰을 ProcessBuilder제대로 칠년 전에서 세 가지 답변에서 언급 한 바와 같이 차이입니다.
dave_thompson_085

질문이 얼마나 오래되었는지는 중요하지 않습니다. 그러나 나는 대답을 수정하려고 노력합니다.
Eugene Lopatkin

ProcessBuilder의 환경을 설정할 수 없습니다. 난 단지 ... 환경을 얻을 수 있습니다
Muhtaroglu 이케

참조 docs.oracle.com/javase/7/docs/api/java/lang/... ... 환경 방법을 통해 그들을받은 후 설정 환경
Muhtaroglu 이케

좀 더 자세히 살펴보면 기본적으로 환경이 null임을 알 수 있습니다.
Eugene Lopatkin 2018

14

네, 차이가 있습니다.

  • Runtime.exec(String)메서드 는 명령과 일련의 인수로 분할되는 단일 명령 문자열을 사용합니다.

  • ProcessBuilder생성자 문자열 (변수 인수) 배열 걸린다. 첫 번째 문자열은 명령 이름이고 나머지는 인수입니다. (문자열 목록을 사용하는 대체 생성자가 있지만 명령과 인수로 구성된 단일 문자열을 사용하는 생성자는 없습니다.)

그래서 당신이 ProcessBuilder에게 명령하는 것은 이름에 공백과 다른 쓰레기가있는 "명령"을 실행하는 것입니다. 물론 운영 체제는 해당 이름의 명령을 찾을 수 없으며 명령 실행이 실패합니다.


아니요, 차이가 없습니다. Runtime.exec (String)은 ProcessBuilder의 바로 가기입니다. 지원되는 다른 생성자가 있습니다.
marcolopes

2
당신은 틀 렸습니다. 소스 코드를 읽어보세요! Runtime.exec(cmd)는 사실상 Runtime.exec(cmd.split("\\s+")). ProcessBuilder클래스에 직접 해당하는 생성자가 없습니다 Runtime.exec(cmd). 이것이 내가 내 대답에서 말하는 요점입니다.
Stephen C

1
실제로 다음과 같이 ProcessBuilder를 인스턴스화 new ProcessBuilder("command arg1 arg2")하면 start()호출이 예상 한대로 수행되지 않습니다. 아마도 실패 할 것이고 이름에 공백이있는 명령이있는 경우에만 성공할 것입니다. 이것은 OP가 요구하는 문제입니다!
Stephen C
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.