miércoles, 2 de septiembre de 2009

Runtime.exec con espacios en blanco para mysqldump

En esta entrada vamos a tratar un tema que me ha llevado varios días de cabeza. Les comento un poco para que se pongan en situación.
Estoy terminando una aplicación para la gestión de una clínica, es por eso junto con que era verano por lo que me he cogido un largo descanso con el blog, y de vuelta al cole me he encontrado con que necesitaba realizar copias de seguridad desde mi aplicación y a poder ser automáticamente, para que el cliente solo deba indicar la carpeta de destino. La aplicación la he realizado con Java y trabaja contra una base de datos MySQL, ya se que con el MySQLAdministrator es muy facil hacer copias pero los clientes solo quieren tener cosas practicas.
Googleando un poco pude comprobar que simplemente necesitaba ejecutar una serie de comandos junto con el PATH del fichero desde el metodo exec de Runtime:
--> Runtime().getRuntime().exec("mysqldump -h HOST -u ...-r PATH_FICHERO")

¿¿¿Cual ha sido mi sorpresa??? Pues cuando ya funcionaba correctamente y conseguía almacenar las copias de seguridad descubrí que si el directorio tenia espacios en blanco los comandos que recibia mysqldump se desordenaban después de la variable que contenía el PATH.
El problema reside en que el metodo exec(string) realiza una llamada a la clase StringTokenizer para obtener un vector con cada uno de los parametros, para ejecutar el metodo exec(nom_comando, vector_parametros[]). Este vector se compone de todas las palabras del String que esten separadas por espacios en blanco y por lo tanto si el PATH contiene alguno lo divide en dos partes como si la segunda fuera otro parámetro nuevo.
Despues de varios intentos intentando acertar como insertar los parámetros para que funcionase correctamente decidí implementar mi solución:

Primero, identificar que posición ocupa la variable que almacenara el PATH con posibles espacios. En mi caso ocupa el último lugar: dest.getCanonicalPath() (/home/pato_donald/copia seguridad)

String cnd = "mysqldump -h" + host + " -u" + user + " -P" + puerto + " -p" + pass + " -B base_datos" +"-r"+ dest.getCanonicalPath();

Segundo, convertir la cadena en un vector de tokens

StringTokenizer st = new StringTokenizer(cnd);

Tercero, realizar un bucle para que añada los tokens en el vector de Strings[] que debe recibir "exec".

int i=0;
while (st.hasMoreTokens()) {
coma[i]=st.nextToken() ;
System.out.println(coma[i] +" n:" + i);
i++;
}
Cuarto y final, modifica la posición del vector que debe contener el PATH con el espacio en blanco.

coma[7]= "-r"+ dest.getCanonicalPath();


String cnd = "mysqldump -h" + host + " -u" + user + " -P" + puerto + " -p" + pass + " -B base_datos" +"-r"+ dest.getCanonicalPath();
StringTokenizer st = new StringTokenizer(cnd);
String[] coma = new String[st.countTokens()+1];
int i=0;
while (st.hasMoreTokens()) {
coma[i]=st.nextToken() ;
System.out.println(coma[i] +" n:" + i);
i++;
}
coma[7]= "-r"+ dest.getCanonicalPath();

Process dumpProcess = Runtime.getRuntime().exec(coma);

Despues de ejecutar esto, el resultado del println sería:
mysqldump
-h127.0.0.1
-uUSER
-P3306
-pPASSWORD
-B
base_datos
-r/home/pato_donald/copias seguridad

Y la ejecución del metodo "exec" se realizaría correctamente sin la alteración de los paramteros.

Espero que les sirva de ayuda tanto como me ha servido a mí.

Un saludo ;)

5 comentarios:

  1. Que bueno que encontre este documento, ya me estaba partiendo la cabeza, con esto del respaldo multiplataforma, solo adiciono que en windows funciona solo ejecutando el string pero en linux si que me ha hecho sufrir

    Felicitaciones por el documento

    Geovanny

    ResponderEliminar
  2. Me alegro que te sea útil el código, a mi me llevo bastante trabajo conseguir que funcionase.
    Por cierto, un detalle importante que he descubierto hace poco es que MYSQL ya no recomienda utilizar el MySQLAdministrator, que es el programa que lleva el MysqlDump y permite hacer las copias de seguridad.
    Te lo comento por que con MySQLDump el código funciona correctamente, pero sin él habrá que ver que programa tendremos que utilizar al realizar la llamada con runtime.
    Un saludo.

    ResponderEliminar
  3. Amigo Lechon, Sabes si se pueden ejecutar 2 comandos dentro del mismo exec?
    Es que quiero ejecutar comandos de cmd dentro del exec, para copiar archivos. Mi programa ya consigue la dirección del archivo y la dirección del destino(en donde se va a copiar).
    dejo mi correo por si tienes alguna respuesta.
    car_19.91@hotmail.com

    De antemano Gracias.

    ResponderEliminar
  4. Gracias, sabia de dónde venía el problema pero no como solucionarlo, ahora probaré a ver si me funciona

    ResponderEliminar
  5. Nooo mi pana lo que estas es loco y fumao, aqui esta la solucion en una sola linea:
    https://blogs.oracle.com/thejavatutorials/entry/changes_to_runtime_exec_problems

    ResponderEliminar