Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running interactive shell program in Java

Tags:

java

exec

I am trying (and failing) to work out how I can run a fully interactive shell program from within Java.

Here's the scenario:

I have a large GUI application which is cross platform and written in Java. Into that I am trying to add an interactive command line environment for running headless. That side of things is all fine and dandy. However, one of the functions of the main GUI is editing of files. Now, for the command line interface I want to be able to execute an external editor to edit the files, then return back to where I was after I have saved and exited. For example, on Linux it may execute "vi /path/to/file".

So how can I execute that command in such a way that the keyboard and display are fully interactive to the application? I don't want it to run in the background. I don't want it to redirect IO through Java, I just want that one single command to run "in the foreground" until it exists.

Just like as if I'd used the system() function in C.

Everything I have found so far runs commands in the background or pipe the IO through Java, which won't work for interactive (non line-mode) applications.

Oh, and one final caveat: I'm restricted to Java 1.6 compatibility, so I can't do fancy things with ProcessBuilder.

Here's the obligatory SSCCE:

class exetest {
    public static void main(String[] args) { 
        try {
            Process p = Runtime.getRuntime().exec("vi");
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

I would expect the Runtime.getRuntime().exec() to block until I had finished in vi, and during that time all keyboard input would go direct (RAW as it's known) through to vi and all screen output would come directly back, untouched, to the user.

And this is how I would achieve it in C:

void main() {
    system("vi");
}

Update:

This achieves the desired result, but a) is limited to Linux / OSX (not that much of a problem, but would be nice to have it cross platform), and b) is a terrible kludge:

import java.io.*;

class exetest {
    public static void main(String[] args) {
        try {
            Process p = Runtime.getRuntime().exec("/bin/bash");
            OutputStream stdin = p.getOutputStream();
            PrintWriter pw = new PrintWriter(stdin);
            pw.println("vi < /dev/tty > /dev/tty");
            pw.close();
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
like image 988
Majenko Avatar asked Apr 19 '15 17:04

Majenko


2 Answers

The exact equivalent of system("vi") is:

final ProcessBuilder p = new ProcessBuilder("vi");
p.redirectInput(Redirect.INHERIT);
p.redirectOutput(Redirect.INHERIT);
p.redirectError(Redirect.INHERIT);

p.start().waitFor();

(I know this is with 3 years delay, but for the others with same problem)

like image 130
MaanooAk Avatar answered Sep 19 '22 16:09

MaanooAk


I declare in advance that this is a terrible kludge, but given your constraints, I'm not sure you'll find something more elegant.

Also, it will not work on Windows.

Use bash:

class Exetest {
    public static void main(String[] args) { 
        try {
            // Create a one-liner for bash
            // Note that the trick is to do all the redirects and
            // give the name of the file in a single argument.
            String [] commandline = new String[3];
            commandline[0] = "bash";
            commandline[1] = "-c";
            commandline[2] = "vi </dev/tty >/dev/tty test.txt";
            Process p = Runtime.getRuntime().exec(commandline);
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

When done this way, you can craft your actual command in run-time.

like image 40
RealSkeptic Avatar answered Sep 21 '22 16:09

RealSkeptic