Search code examples
javalinuxbashprocessbuilderjline

How to run interactive bash using Java ProcessBuilder API?


I am trying to start bash using java ProcessBuilder API:

code:

public class BashExecutor {
    public void executeCommand(String[] command, Consumer<String> consumer) {
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(command);
        processBuilder.redirectErrorStream(true);
        processBuilder.inheritIO();
        //processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);
        //processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        try {
            Process process = processBuilder.start();
            /*ProcessStreamConsumer streamConsumer = new ProcessStreamConsumer(process.getInputStream(), consumer);
            streamConsumer.consumeStream();*/
            process.waitFor();
            if(process.isAlive()){
                process.destroyForcibly();
            }
        } catch (Exception e) {
            consumer.accept(e.getMessage());
        }
    }

ProcessStreamConsumer:

public class ProcessStreamConsumer {
    private InputStream inputStream;
    private Consumer<String> consumer;

    public ProcessStreamConsumer(InputStream inputStream, Consumer<String> consumer) {
        this.inputStream = inputStream;
        this.consumer = consumer;
    }

    public void consumeStream() {
        new BufferedReader(new InputStreamReader(inputStream)).lines()
                .forEach(consumer);
    }
}

Main class:

class Console{
  public final ConsoleReader console;
  public final BashExecutor bashExecutor;

  public Console() throws IOException {
    console = new ConsoleReader();
    console.setExpandEvents(false);
    bashExecutor = new BashExecutor();
  }

  public void println(Object in){
    try {
        console.println(in.toString());
        console.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    Console consoleMain = new Console();
    String input = consoleMain.console.readLine("grid>");
    while(true){
        if(input==null) input= "stop";

        input = input.trim();

        while(input.trim().equals("")){
            input = console.readLine(grid>);
        }

        if(input.startsWith("stop") || input.startsWith("qui")){
            System.exit(0);
        }
        if(input.startsWith("bash")) {
            bashExecutor.executeCommand(input, this::println);
            input=console.readLine("grid>");
            continue;
        }
  } 
}

The idea is that I have console app which I have created using the Jline console library which has some custom commands which I have created/will create.

But I want to be able to execute bash (interactive mode) in this console so that users dont have to quit my console app if they want to execute some bash commands.

with current code that I have it does start the bash and I am able to execute the bash commands as well, But I dont see the input that I am typing to the bash prompt.

eg output:

grid>bash
bash-4.2$ Mon Jun 15 02:57:14 EDT 2020
bash-4.2$ bash: wrongcommand: command not found
bash-4.2$ exit
grid>quit

If you see the above o/p I cannot see the commands that I had typed(date, wrongcommand) but I could execute them and get the output.

I tried using the code that is commented as well, but that just hangs and no prompt is displayed on the screen itself.

Questions:

  1. why cant I see the command I typed in the terminal where I run this?
  2. I tried using RedirectInput, RedirectOutput as PIPE as well, but the code hangs and I am unable to see anything on the screen after I run bash command
  3. What should I do to make this work?
  4. Could I also load a bashrc file as profile as it has aliases, when I run the bash command through processBuilder?

I checked this: Invoking bash from Java interactively

but I could not understand the answer. will bash prompt only when it is invoked from the terminal?


Solution

  • This worked well

    ProcessBuilder processBuilder = new ProcessBuilder();
            try {
                processBuilder.command("stty", "echo");
                processBuilder.inheritIO();
                processBuilder.start().waitFor(1000, TimeUnit.MILLISECONDS);
                processBuilder.command("bash", "-i");
                processBuilder.redirectErrorStream(true);
                processBuilder.inheritIO();
                processBuilder.start().waitFor();
                processBuilder.command("stty", "-echo");
                processBuilder.inheritIO();
                processBuilder.start().waitFor(1000, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                consumer.accept("exception occurred while starting bash");
                consumer.accept(e.getMessage());
            }
    

    using which I was able to see what I was typing, but I don't know why it works. if anyone has a better answer please do post it.