Estoy experimentando con una carcasa de borde que estamos viendo en producción. Tenemos un modelo comercial donde los clientes generan archivos de texto y luego los transfieren a nuestros servidores FTP. Ingestamos esos archivos y los procesamos en nuestro backend Java (ejecutándose en máquinas CentOS). La mayoría (95% +) de nuestros clientes saben que generan estos archivos en UTF-8, que es lo que queremos. Sin embargo, tenemos algunos clientes obstinados (pero cuentas grandes) que generan estos archivos en la máquina Windows con el juego de caracteres CP1252. Sin embargo, no hay problema, configuramos nuestras librerías de terceros (que son lo que hace la mayor parte del trabajo de "procesamiento") para manejar la entrada en cualquier conjunto de caracteres a través de un voo doo mágico.Java no puede ver el archivo en el sistema de archivos que contiene caracteres ilegales
Ocasionalmente, vemos un archivo que tiene caracteres UTF-8 ilegales (CP1252) en su nombre. Cuando nuestro software intenta leer estos archivos desde el servidor FTP el método normal de las estrangulaciones de lectura de archivos y lanza un FileNotFoundException
:
File f = getFileFromFTPServer();
FileReader fReader = new FileReader(f);
String line = fReader.readLine();
// ...etc.
Las excepciones ser algo como esto:
java.io.FileNotFoundException: /path/to/file/some-text-blah?blah.xml (No such file or directory) at java.io.FileInputStream.open(Native Method) at
java.io.FileInputStream.(FileInputStream.java:120) at java.io.FileReader.(FileReader.java:55) at com.myorg.backend.app.InputFileProcessor.run(InputFileProcessor.java:60) at
java.lang.Thread.run(Thread.java:662)
Así que lo que piensan está sucediendo es que debido a que el archivo nombre en sí mismo contiene caracteres ilegales, nunca llegamos a leerlo en primer lugar. Si pudiéramos, independientemente del contenido del archivo, nuestro software debería poder procesarlo correctamente. Así que esto es realmente un problema con la lectura de nombres de archivo con caracteres UTF-8 ilegales en ellos.
Como caso de prueba, creé una "aplicación" de Java muy simple para implementar en uno de nuestros servidores y probar algunas cosas (el código fuente se proporciona a continuación). Luego inicié sesión en una máquina con Windows y creé un archivo de prueba y lo llamé test£.txt
. Observe el personaje después de "prueba" en el nombre del archivo. Este es Alt-0163. Envié esto por FTP a nuestro servidor, y cuando ejecuté ls -ltr
en su directorio padre, me sorprendió verlo listado como test?.txt
.
Antes de ir más lejos, aquí es el de Java "aplicación" que escribí para la prueba/reproducción de este tema:
public Driver {
public static void main(String[] args) {
Driver d = new Driver();
d.run(args[0]); // I know this is bad, but its fine for our purposes here
}
private void run(String fileName) {
InputStreamReader isr = null;
BufferedReader buffReader = null;
FileInputStream fis = null;
String firstLineOfFile = "default";
System.out.println("Processing " + fileName);
try {
System.out.println("Attempting UTF-8...");
fis = new FileInputStream(fileName);
isr = new InputStreamReader(fis, Charset.forName("UTF-8"));
buffReader = new BufferedReader(isr);
firstLineOfFile = buffReader.readLine();
System.out.println("UTF-8 worked and first line of file is : " + firstLineOfFile);
}
catch(IOException io1) {
// UTF-8 failed; try CP1252.
try {
System.out.println("UTF-8 failed. Attempting Windows-1252...(" + io1.getMessage() + ")");
fis = new FileInputStream(fileName);
// I've also tried variations "WINDOWS-1252", "Windows-1252", "CP1252", "Cp1252", "cp1252"
isr = new InputStreamReader(fis, Charset.forName("windows-1252"));
buffReader = new BufferedReader(isr);
firstLineOfFile = buffReader.readLine();
System.out.println("Windows-1252 worked and first line of file is : " + firstLineOfFile);
}
catch(IOException io2) {
// Both UTF-8 and CP1252 failed...
System.out.println("Both UTF-8 and Windows-1252 failed. Could not read file. (" + io2.getMessage() + ")");
}
}
}
}
Cuando ejecuto esto desde el terminal (java -cp . com/Driver t*
), me sale el siguiente resultado:
Processing test�.txt
Attempting UTF-8...
UTF-8 failed. Attempting Windows-1252...(test�.txt (No such file or directory))
Both UTF-8 and Windows-1252 failed. Could not read file.(test�.txt (No such file or directory))
test�.txt
?!?! Investigué un poco y descubrí que el "�" es el personaje de reemplazo de Unicode \uFFFD
. Entonces, , supongo lo que está sucediendo es que el servidor FTP de CentOS no sabe cómo manejar Alt-0163 (£
) y lo reemplaza con \uFFFD
(�
). Pero no entiendo por qué ls -ltr
muestra un archivo llamado test?.txt
...
En cualquier caso, parece que la solución es agregar algo de lógica que busque la existencia de este carácter en el nombre del archivo, y si se encuentra , cambia el nombre del archivo a otra cosa (como quizás hacer un String-wise replaceAll("\uFFFD", "_")
o algo así) que el sistema puede leer y procesar.
El problema es que Java ni siquiera ver este archivo en el sistema de archivos. CentOS sabe que el archivo está allí (test?.txt
), pero cuando ese archivo pasa a Java, Java lo interpreta como test�.txt
y por alguna razón No such file or directory
...
¿Cómo puedo conseguir Java para ver este archivo para que pueda realizar una File::renameTo(String)
en él? Perdón por la historia de fondo aquí, pero creo que es relevante ya que cada detalle cuenta en este escenario. ¡Gracias por adelantado!
por lo que no puede listar los archivos en el directorio, luego ver cuáles tienen "caracteres impares" en su nombre y cambiarles el nombre a "indicación de fecha y hora + al azar.algo" con file.renameTo? –
@MarkusMikkolainen - ¿Estás hablando de hacer esto manualmente? Si no, ¿a qué idioma/guión se está refiriendo? – IAmYourFaja
Sugiero que use objetos File en lugar de pasar nombres de archivos. eso probablemente evitará cualquier corrupción de nombre de archivo. –