2

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\"
asked May 17, 2017 at 18:57
4
  • Do you really want a 'terminal'? Or do you just want to execute some shell commanfds? Commented 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. Commented 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. Commented 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. Commented May 20, 2017 at 18:09

2 Answers 2

2
+50

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).

answered May 20, 2017 at 23:03
Sign up to request clarification or add additional context in comments.

8 Comments

Just to verify, that we are on the same page, does your code call a terminal and open the terminal so the user can see what is going on and the prints go to that terminal? (I am asking, because you are gathering the Inputstream and printing it out)
Holly Jesus! Your code does work! And I was about to go the script route where I had to create executable scripts and all that nonsense. A small addition that I would like to have: How can I disable the horribly spammy Mac OS process terminal? (Things like "last Login: .." and the "logout", "saving session", etc). Also what would I have to add to give a "press any button to exit" to the Linux command? Thank you so much for the help so far :D
Sorry I don't know if you can control that output programmatically. Maybe there are options within the Terminal app. About your first question, the output that is logged is the one from the terminal itself, just in case (macOS Terminal does print 1 line when invoked this way).
For xterm there is no button user can press to exit, other than the window close button. Did you mean key? Maybe append ; echo 'Press Enter to close...' && read to the command
Ok thanks about the mac thing. Maybe it is worth posting a seperate question about that? (I don' even know how I can ask that :S)
|
0

String[] 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.

answered May 20, 2017 at 4:43

1 Comment

Unfortunately this doesn't work. Also the linked thread is about executing custom programs, where as I am trying to just start the terminal and pass it some arguments.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.