Ejecutar comandos Linux desde Java (II)
La primera parte se encuentra aquí.
En la entrada anterior vimos un ejemplo sencillo sobre cómo podíamos ejecutar un comando externo desde el código Java. Voy a intentar construir algo más complejo que esto, concretamente me interesa hacer una clase que pueda lanzar un comando Linux sin bloquear el hilo que lo origina. Es decir, lanzar un comando como si fuera un Thread más de la máquina virtual.
La forma más sencilla que conozco para hacerlo sin ningún tipo de bloqueos es mediante un Listener. A mi me interesa ver en tiempo real la STDOUT y STDERR del comando externo. También quiero saber cuando acaba y cuál es el código de salida. Construyo una interfaz:
public interface ProcessListener {
// se llama cuando se actualiza la salida estándar
public void updateStdOut(byte[] b);
// se llama cuando se actualiza StdErr
public void updateStdErr(byte[] b);
// se llama cuando el proceso termina
public void processFinish(int exitCode, byte[] stdOut, byte[] stdErr);
}
|
Esta será la interfaz que implementaremos en una clase que será la responsable de realizar ciertas operaciones en función de las distintas salidas del comando exterior. Ahora necesitamos una clase que será la encargada de lanzar el proceso propiamente dicho:
public class ProcessLauncher implements Runnable {
private String Command;
private int ExitCode = Integer.MIN_VALUE;
private Thread MyThread;
private Vector StdOut;
private Vector StdErr;
private ProcessListener MyProcessListener;
private Object Lock = new Object();
private Process MyProcess;
public ProcessLauncher(String command, ProcessListener pl) {
Command = command;
MyProcessListener = pl;
StdOut = new Vector();
StdErr = new Vector();
MyThread = new Thread(this);
MyThread.start();
}
public boolean isFinished() {
return ExitCode != Integer.MIN_VALUE;
}
public void run() {
try {
// arranco el proceso
MyProcess = startProcess();
// creo un controlador del proceso
ProcessController pc = new ProcessController(MyProcess, this);
// ahora hasta que no finalice el método voy leyendo la salida
while(ExitCode == Integer.MIN_VALUE) {
synchronized(Lock) {
// de esta forma me aseguro que nadie más está leyendo la salida
readStreams();
}
Thread.sleep(500); // duermo un poco para liberar los recursos
}
}catch(Exception e){
e.printStackTrace();
}
}
private void readStreams() throws Exception {
InputStream stdout = MyProcess.getInputStream();
InputStream stderr = MyProcess.getErrorStream();
byte[] b;
int size;
size = stdout.available();
if(size != 0) {
b = new byte[size];
stdout.read(b);
MyProcessListener.updateStdOut(b);
for(int i = 0; i < b.length; i++) {
StdOut.add(b[i]);
}
}
size = stderr.available();
if(size != 0) {
b = new byte[size];
stderr.read(b);
MyProcessListener.updateStdErr(b);
for(int i = 0; i < b.length; i++) {
StdErr.add(b[i]);
}
}
}
private Process startProcess() throws Exception {
Process proc = Runtime.getRuntime().exec(Command);
return proc;
}
public void notifyExit(int exitCode) throws Exception {
// me notifican que se el proceso se ha terminado
ExitCode = exitCode;
// leo por última vez las salidas, por si se ha quedado algo
synchronized (Lock) {
readStreams();
}
// notifico al listener
byte[] stdOut = new byte[StdOut.size()];
for(int i = 0; i < stdOut.length; i++) {
stdOut[i] = ((Byte)StdOut.get(i)).byteValue();
}
byte[] stdErr = new byte[StdErr.size()];
for(int i = 0; i < stdErr.length; i++) {
stdErr[i] = ((Byte)StdErr.get(i)).byteValue();
}
MyProcessListener.processFinish(exitCode, stdOut, stdErr);
}
}
|
Esta clase se encargará de leer periodicamente los flujos de salida del proceso e ir tanto guardándolo en memoria como pasándolo al Listener en tiempo real. Pero claro, aquí nos encontramos con otra dificultad y es que resulta que queremos saber a ciencia cierta y en el momento exacto cuando termina nuestro proceso, para ello definimos un controlador de proceso (ProcessController):
public class ProcessController implements Runnable {
private Process MyProcess;
private Thread MyThread;
private ProcessLauncher MyProcessLauncher;
public ProcessController(Process proc, ProcessLauncher pl) {
MyProcess = proc;
MyProcessLauncher = pl;
MyThread = new Thread(this);
MyThread.start();
}
public void run() {
try {
int code = MyProcess.waitFor();
// notificar al padre
MyProcessLauncher.notifyExit(code);
}catch(Exception e){
e.printStackTrace();
}
}
}
|
El objeto de esta clase se ejecutará en un hilo aparte que se quedará bloqueado hasta que el proceso termine. En cuanto termina el proceso el código de salida es comunicado a ProcessLauncher para que este pueda tomar las acciones oportunas. Para la próxima vez pondré ejemplos de utilización y si me da tiempo prepararé un JAR para que se pueda bajar y usar tranquilamente como si fuera una librería.
Esta entrada se publicó , el Lunes, 17 de Agosto de 2009 a las 19:11 horas y está guardada en Java, Soluciones. Puedes seguir cualquier respuesta a esta entrada en el RSS 2.0.
Puedes dejar un comentario o enviar un trackback desde tu propio sitio.
[...] Ejecutar comandos Linux desde Java (II) Copias de seguridad de MySQL con mysqldump [...]
podrias poner los ejemplos de utilizacion?
muy buen blog.
saludos