I have a seemingly trivial problem: I want to start a terminal from a java process and give the terminal one or two commands. I have a simple example code, that works perfectly on windows with CMD. But I have not been able to achieve the same exact behavior on a Linux nor a Mac OS machine. I am aware, that the command needs to be changed, but unfortunately I have not been able to pass a string of arguments to a terminal on Mac.
Here the working code for windows:
import java.lang.ProcessBuilder.Redirect;
public class ExecTest {
public static void main(String[] args){
String cmd = "cmd /c start cmd.exe /K \"echo hello && echo bye\"";
try {
Process p = Runtime.getRuntime().exec(cmd);
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
On lubuntu I have been able to create a terminal with this command:
lxterminal -l -e 'echo hello && echo bye && read'
But this only works if called by a terminal but not with the java process.
.
TLDR: What is the Linux and Mac equivalent of this command:
cmd /c start cmd.exe /K \"echo hello && echo bye\"
-
Do you really want a 'terminal'? Or do you just want to execute some shell commanfds?bmargulies– bmargulies2017年05月20日 01:43:32 +00:00Commented May 20, 2017 at 1:43
-
I am able to open a terminal with command 'gnome-terminal' and same java code on my Ubuntu machine. Could you please try with this.Vip– Vip2017年05月20日 04:34:47 +00:00Commented May 20, 2017 at 4:34
-
@bmargulies Yes. To give some context: I have made a Code Editor, that can compile some code. I have also created my own terminal that shows the result of the exec process. But there are some edge cases where I can't fully display the In and Outputstreams. So as a backup, I want to allow the user to execute the code in a native terminal. So yes I want to spawn a fresh native terminal and execute some code.Haeri– Haeri2017年05月20日 18:05:01 +00:00Commented May 20, 2017 at 18:05
-
@VipulGoyal I am not sure what you are suggesting.. I am aware that I can open a terminal via a java process, by just calling the name of the terminal, but the main problem is that I can't provide any commands to that terminal.Haeri– Haeri2017年05月20日 18:09:27 +00:00Commented May 20, 2017 at 18:09
2 Answers 2
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).
8 Comments
; echo 'Press Enter to close...' && read to the commandString[] cmd = {"echo", "hello", "&&", "echo", "hi"};
Runtime.getRuntime().exec(cmd);
There are multiple ways to do this, but this should work for you. Executables on Mac should run automatically in Terminal.
Possibly similar to: How To Run Mac OS Terminal Commands From Java (Using Runtime?) If anything they have a few methods for running scripts in the terminal.