I suggest you use ProcessBuilder
to benefit from easier output redirection and ability to consume it without using threads, and also pass the command as a String[]
instead of flat String
to be able to support the various wrapping approaches. If you prefer to stick with Runtime.exec()
, it also supports String[]
, but the example below uses ProcessBuilder
.
static int executeInTerminal(String command) throws IOException, InterruptedException {
final String[] wrappedCommand;
if (isWindows) {
wrappedCommand = new String[]{ "cmd", "/c", "start", "/wait", "cmd.exe", "/K", command };
}
else if (isLinux) {
wrappedCommand = new String[]{ "xterm", "-e", "bash", "-c", command};
}
else if (isMac) {
wrappedCommand = new String[]{"osascript",
"-e", "tell application \"Terminal\" to activate",
"-e", "tell application \"Terminal\" to do script \"" + command + ";exit\""};
}
else {
throw new RuntimeException("Unsupported OS ☹");
}
Process process = new ProcessBuilder(wrappedCommand)
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // Your superior logging approach here
}
}
return process.waitFor();
}
Tested on 3 operating systems. The 3 booleans is{Windows|Linux|Mac}
are unexplained here as OS detection is another topic, so for the example I kept it simple and handled out of the method.
The ProcessBuilder
is configured to redirect stderr to stdout, so that a single stream needs to be read. That stream is then read and logged, because you must consume stderr and stdout, in case the Terminal itself prints stuff (not the command you are running in the Terminal, this is about what the Terminal itself prints), if it prints too much you risk getting the process to block indefinitely, waiting for the buffer to be read. See this nice answer.
For macOS if the command you pass is always a single executable script, you could also use {"open", "-a", "Terminal", command}
, but that will not work with echo hello && echo bye
. Similarly you could use {"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", command}
, which would get you a second instance of the app running, but with same limitations.
Final note: you can offer a reasonable basic implementation, but you'll probably need to make it configurable to allow alternative terminal applications (especially on Linux where it varies a lot).