Descarga de archivos de texto con Python y ftplib.FTP desde z / os

Estoy tratando de automatizar la descarga de algunos archivos de texto desde z / os PDS, usando Python y ftplib.

Dado que los archivos del host son EBCDIC, no puedo simplemente usar FTP.retrbinary ().

FTP.retrlines (), cuando se usa con open (file, w) .writelines como callback, no proporciona EOL, por supuesto.

Así que, para empezar, se me ocurrió este código que “me parece bien”, pero como soy un noob relativo de Python, ¿alguien puede sugerir un mejor enfoque? Obviamente, para mantener esta pregunta simple, esto no es lo último, campanas y silbidos.

Muchas gracias.

#!python.exe from ftplib import FTP class xfile (file): def writelineswitheol(self, sequence): for s in sequence: self.write(s+"\r\n") sess = FTP("zos.server.to.be", "myid", "mypassword") sess.sendcmd("site sbd=(IBM-1047,ISO8859-1)") sess.cwd("'FOO.BAR.PDS'") a = sess.nlst("RTB*") for i in a: sess.retrlines("RETR "+i, xfile(i, 'w').writelineswitheol) sess.quit() 

Actualización: Python 3.0, la plataforma es MingW en Windows XP.

Los PDS de z / os tienen una estructura de registro fija, en lugar de confiar en los finales de línea como separadores de registro. Sin embargo, el servidor FTP z / os, cuando se transmite en modo de texto, proporciona los finales de grabación, que se retira ().

Actualización de cierre:

Aquí está mi solución revisada, que será la base para el desarrollo continuo (eliminar contraseñas incorporadas, por ejemplo):

 import ftplib import os from sys import exc_info sess = ftplib.FTP("undisclosed.server.com", "userid", "password") sess.sendcmd("site sbd=(IBM-1047,ISO8859-1)") for dir in ["ASM", "ASML", "ASMM", "C", "CPP", "DLLA", "DLLC", "DLMC", "GEN", "HDR", "MAC"]: sess.cwd("'ZLTALM.PREP.%s'" % dir) try: filelist = sess.nlst() except ftplib.error_perm as x: if (x.args[0][:3] != '550'): raise else: try: os.mkdir(dir) except: continue for hostfile in filelist: lines = [] sess.retrlines("RETR "+hostfile, lines.append) pcfile = open("%s/%s"% (dir,hostfile), 'w') for line in lines: pcfile.write(line+"\n") pcfile.close() print ("Done: " + dir) sess.quit() 

Mi agradecimiento a John y Vinay

Acabo de encontrar esta pregunta cuando intentaba descubrir cómo descargar recursivamente conjuntos de datos desde z / OS. He estado usando un simple script de Python durante años para descargar archivos ebcdic desde el mainframe. Efectivamente solo hace esto:

 def writeline(line): file.write(line + "\n") file = open(filename, "w") ftp.retrlines("retr " + filename, writeline) 

Debería poder descargar el archivo como un binario (usando retrbinary ) y usar el módulo de codecs para convertir de EBCDIC a cualquier encoding de salida que desee. Debe conocer la página de códigos EBCDIC específica que se está utilizando en el sistema z / OS (por ejemplo, cp500). Si los archivos son pequeños, incluso podría hacer algo como (para una conversión a UTF-8):

 file = open(ebcdic_filename, "rb") data = file.read() converted = data.decode("cp500").encode("utf8") file = open(utf8_filename, "wb") file.write(converted) file.close() 

Actualización: si necesita usar retrlines de retrlines para obtener las líneas y sus líneas están regresando en la encoding correcta, su enfoque no funcionará, porque la callback se llama una vez para cada línea. Por lo tanto, en la callback, la sequence será la línea, y su bucle for escribirá caracteres individuales en la línea a la salida, cada uno en su propia línea . Por lo tanto, es probable que desee realizar self.write(sequence + "\r\n") lugar del bucle for . Sin embargo, todavía no se siente especialmente correcto en el file subclases solo para agregar este método de utilidad, probablemente deba estar en una clase diferente en su versión de bells-and-whistles .

El método writelineswitheol agrega ‘\ r \ n’ en lugar de ‘\ n’ y luego escribe el resultado en un archivo abierto en modo texto. El efecto, sin importar en qué plataforma se esté ejecutando, será un ‘\ r’ no deseado. Simplemente agregue ‘\ n’ y obtendrá el final de línea apropiado.

El manejo adecuado de errores no debe ser relegado a una versión de “campanas y silbidos”. Debe configurar su callback para que su archivo abierto () esté en un bash / excepto y conserve una referencia al identificador del archivo de salida, su llamada de escritura esté en un bash / excepto, y usted tenga un método de callback_obj.close () que se usa cuando retrlines () regresa explícitamente a file_handle.close () (en un try / except) – de esa manera se obtiene un manejo explícito de errores, por ejemplo, los mensajes “no pueden (abrir | escribir para | cerrar) el archivo X porque Y” Y se ahorra tener que pensar cuándo se van a cerrar implícitamente los archivos y si corre el riesgo de quedarse sin los manejadores de archivos.

Python 3.x ftplib.FTP.retrlines () debería proporcionarle objetos str que son en efecto cadenas Unicode, y deberá codificarlos antes de escribirlos, a menos que la encoding predeterminada sea latin1, lo que sería bastante inusual para Windows caja. Debe tener archivos de prueba con (1) todos los 256 bytes posibles (2) todos los bytes que son válidos en la página de códigos EBCDIC esperada.

[Algunos comentarios de “saneamiento”]

  1. Debería considerar actualizar su Python de 3.0 (una versión de “prueba de concepto”) a 3.1.

  2. Para facilitar una mejor comprensión de su código, use “i” como un identificador solo como un índice de secuencia y solo si adquirió irremisiblemente el hábito de FORTRAN 3 o más décadas atrás 🙂

  3. Dos de los problemas descubiertos hasta ahora (al añadir terminador de línea a cada carácter, terminador de línea incorrecto) se habrían mostrado la primera vez que lo probaste.