java shell for executing/coordinating processes?

2020-03-25 08:27发布

问题:

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:

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.



回答2:

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



回答3:

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.



回答4:

You can use the ProcessBuilder API provided by java.

Runtime.getRuntime().exec(...) take either an array of strings or a single string. The single-string overloads of exec() will tokenise the string into an array of arguments, before passing the string array onto one of the exec() overloads that takes a string array. The ProcessBuilder constructors, on the other hand, only take a varargs array of strings or a List of strings, where each string in the array or list is assumed to be an individual argument. Either way, the arguments obtained are then joined up into a string that is passed to the OS to execute.

Find more details at the below link Difference between ProcessBuilder and Runtime.exec()

Sample program to execute the commands.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.List;

public class ProcessBuilderTest {
    static ProcessBuilder processBuilder = null;
    static Process spawnProcess = null;
    static int exitValue;
    static int pid;
    static List<String> commands;

    public static void main(String[] args) {
        runSqoop();
    }

    public static void runSqoop() {
        String[] commands = { "ssh", "node", "commands" };
        processBuilder = new ProcessBuilder(commands);
        try {
            System.out.println("Executing " + commands.toString());
            spawnProcess = processBuilder.inheritIO().start();
            try {
                exitValue = spawnProcess.waitFor();
                pid = getPID(spawnProcess);
                System.out.println("The PID is " + pid);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            System.out.println("Process exited with the status :" + exitValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static int getPID(Process process) {
        try {
            Class<?> processImplClass = process.getClass();
            Field fpid = processImplClass.getDeclaredField("pid");
            if (!fpid.isAccessible()) {
                fpid.setAccessible(true);
            }
            return fpid.getInt(process);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return -1;
        }
    }

}


标签: java shell