How can I launch VI from within Java under commons

2019-01-28 07:48发布

问题:

I would like to be able to launch VI from within my Java program and wait for the user to quit VI before proceeding. Here's the code snippet that I have currently:

...    
String previewFileName="test.txt"; // the file to edit
CommandLine cmdLine = new CommandLine("/usr/bin/vi");
cmdLine.addArgument(previewFileName);
cmdLine.addArgument(">/dev/tty");
cmdLine.addArgument("</dev/tty");

Executor executor = new DefaultExecutor();
try
{
    DefaultExecuteResultHandler resultHandler = new ResetProcessResultHandler(cmdLine);
    executor.execute(cmdLine, resultHandler);
} catch (IOException e)
{
    throw new Error("Cannot execute command: /usr/bin/vi " + previewFileName, e);
}
log.info("waiting...");
cmdLine.wait();
log.info("...done");
...

private class ResetProcessResultHandler extends DefaultExecuteResultHandler
{
    private final CommandLine mCommandLine;
    public ResetProcessResultHandler(CommandLine pCommandLine)
    {
        mCommandLine = pCommandLine;
    }
    public void onProcessComplete(int exitValue)
    {
        log.info("Command complete  rc(" + exitValue + ")");
        if (exitValue != 0)
        {
            throw new RuntimeException("VI command error [rc=" + exitValue + "] " );
        }
        mCommandLine.notify();
    }
    public void onProcessFailed(ExecuteException e)
    {
        if (e.getExitValue() != 0)
        {
            log.error("launch VI error " + e.toString());
            throw new RuntimeException("VI command failed [" + e.getCause() + "] ");
        }
        else
        {
            log.info("VI complete   rc(" + e.getExitValue() + ")");
        }
        mCommandLine.notify();
    }
}

I receive output:

Vim:  output is not to a terminal
Vim:  input is not from a terminal

But then I see the screen painted as if VI had started; and VI doesn't read characters I type.

So ... redirecting from /dev/tty isn't doing the trick.

Someone must have done this before - help!

Thanks,

Mark

回答1:

However since Java 1.7 you can use the next example to transparently redirect and have full console functionality

System.out.println("STARTING VI");
 ProcessBuilder processBuilder = new ProcessBuilder("/usr/bin/vi");
 processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
 processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
 processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT);

 Process p = processBuilder.start();
  // wait for termination.
  p.waitFor();
System.out.println("Exiting VI");

This will allow you to open VI transparently for JVM 1.7+.



回答2:

When Java runs a program via Runtime.exec() (and this is what commons-exec does in the end), it connects the program's input, output and error streams to your Java app as input/output streams. Such a stream is certainly not a terminal, you can't for example move the text cursor in it (since it doesn't have any), change text colors, or detect if Shift key is pressed (since it's just a stream of bytes and not a physical keyborad). So, an interactive app like vi can't really function under such conditions like in a terminal.

By the way, I'm not sure if the command line args you supply are parsed by the shell or passed directly to the program. In the latter case your redirection to /dev/tty couldn't possibly work even if there was a way for Java to somehow allow the program to replace Java's connected streams with something else.

As an aside, it seems a bit strange why you would like to run vi from inside a Java program.

So I guess the best solution is to execute a terminal emulator like konsole or gnome-terminal or xterm and let it run vi by passing corresponding argument on its command line (e.g. konsole -e vi). In this case the terminal's window should pop up and vi could function inside it. Of course, it won't work if you're on a headless server, but then running vi can't be useful anyway.



回答3:

I'm not sure how to do it with commons-exec,

But standard Java should be something along the lines of...

String[] command = {"/usr/bin/vi", "test.txt"};
Process vimProcess = Runtime.getRuntime().exec(command);
vimProcess.waitFor();

This will cause the current thread to wait for the process to complete. You can also use vimProcess.getInputStream(), getOutputStream() and getErrorStream() to redirect those to log files or wherever you want it to go.

See here for more details. http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html

Hopefully this helps.