tags:

views:

355

answers:

3

I know about using Runtime.exec, you pass it a native program to run + arguments. If it's a regular program, you can run it directly. If it's a shell script, you have to run an external shell program like sh or csh or cmd.exe.

Is there some Java class (either standard or open-source) that implements a shell, meaning a program that you pass a command string or a script into, that executes commands and redirects standard I/O/err accordingly, so that you could pass a string like foo | bar > baz.out in, and it would run the foo and bar programs w/o having to run another executable outside of Java?

(and by shell I don't mean BeanShell or the standalone Rhino Javascript interpreter, those are Java implementations to execute Java and Javascript code. I'm talking about Java implementations to execute non-Java executables and handle the plumbing of redirecting I/O.)

+1  A: 

Since JDK 1.5 there is java.lang.ProcessBuilder which handles std and err streams as well. It's sort of the replacement for java.lang.Runtime

initialZero
+2  A: 

You've always been able to handle streams with Runtime.exec

e.g.

String cmd = "ls -al";
 Runtime run = Runtime.getRuntime();
 Process pr = run.exec(cmd);
 pr.waitFor();
 BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
 String line = "";
 while ((line=buf.readLine())!=null) {
  System.out.println(line);
 }

However, if you want to put shell characters such as pipe and redirect in there you'd have to write your own command line parser which links up the streams. As far as I know there hasn't one been written. That being said, could you just invoke bash from Java with a -c "ls | sort" for example and then read the input. Hmm time to do some testing.

Benj
+2  A: 

Ok, I've worked it out:

Basically, you need to invoke bash with a "-s" and then write the full command string to it.

public class ShellExecutor {

private String stdinFlag;
private String shell;

public ShellExecutor(String shell, String stdinFlag) 
{
 this.shell = shell;
 this.stdinFlag = stdinFlag;
}

public String execute(String cmdLine) throws IOException 
{
 StringBuilder sb = new StringBuilder();
    Runtime run = Runtime.getRuntime();
    System.out.println(shell);
    Process pr = run.exec(cmdLine);
    BufferedWriter bufWr = new BufferedWriter(new OutputStreamWriter(pr.getOutputStream()));
    bufWr.write(cmdLine);
    try 
    {
  pr.waitFor();
 } catch (InterruptedException e) {}
    BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
    String line = "";
    while ((line=buf.readLine())!=null) 
    {
        sb.append(line + "\n");
    }
    return sb.toString();
}

}

Then use it like this:

     ShellExecutor excutor = new ShellExecutor("/bin/bash", "-s");
 try {
  System.out.println(excutor.execute("ls / | sort -r"));
 } catch (IOException e) {
  e.printStackTrace();
 }

Obviously, you aught to do something with the error string but this is a working example.

Benj
interesting... in my case I don't need any output to pipe out of the "regular shell" into my Java app, but it seems like it might be useful for some people.
Jason S
Yes, the only downside I can see in doing it this way is what happens if you want to pipe stuff from java into the shell, since you're using the outputstream to pass the command line to the bash, you can't also use it to pipe stuff into the first command. It'd be better if you could use bash -c but I couldn't figure out how to make that work.
Benj