Загрузка текстовых файлов с помощью Python и ftplib.FTP из z/os

Я пытаюсь автоматизировать загрузку некоторых текстовых файлов из z/os PDS, используя Python и ftplib.

Поскольку файлы хоста имеют формат EBCDIC, я не могу просто использовать FTP.retrbinary().

FTP.retrlines() при использовании с open(file,w).writelines в качестве обратного вызова, конечно, не предоставляет EOL.

Итак, для начала я придумал этот фрагмент кода, который «выглядит нормально для меня», но, поскольку я относительный новичок в Python, может ли кто-нибудь предложить лучший подход? Очевидно, чтобы не усложнять этот вопрос, это не последняя вещь, навороты.

Большое спасибо.

#!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()

Обновление: Python 3.0, платформа MingW под Windows XP.

z/os PDS имеют фиксированную структуру записей, вместо того, чтобы полагаться на окончания строк в качестве разделителей записей. Однако FTP-сервер z/os при передаче в текстовом режиме предоставляет окончания записей, которые функция retrlines() удаляет.

Закрытие обновления:

Вот мое исправленное решение, которое станет основой для дальнейшей разработки (например, удаление встроенных паролей):

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()

Моя благодарность Джону и Винаю


person Brent.Longborough    schedule 26.07.2009    source источник
comment
Пожалуйста, отредактируйте свой вопрос, упомянув и описав файлы PDS. некоторые текстовые файлы довольно неадекватны.   -  person John Machin    schedule 26.07.2009
comment
Также укажите, какая платформа, какая версия Python и почему ваш метод writelineswitheol добавляет '\r\n' вместо '\n'. И, пожалуйста, укажите, действительно ли вы запустили это и проверили вывод, чтобы убедиться, что он имеет правильное завершение строки для вашей платформы.   -  person John Machin    schedule 26.07.2009
comment
Сделанный. Я занимаюсь программированием на выходных дома за пределами corp.firewall, так что я буду тестировать эту идею только позже на этой неделе.   -  person Brent.Longborough    schedule 26.07.2009


Ответы (4)


Просто наткнулся на этот вопрос, когда пытался понять, как рекурсивно загружать наборы данных из z/OS. Я уже много лет использую простой скрипт Python для загрузки файлов ebcdic с мейнфрейма. Он эффективно просто делает это:

def writeline(line):
    file.write(line + "\n")

file = open(filename, "w")
ftp.retrlines("retr " + filename, writeline)
person Dave Griffiths    schedule 01.11.2011

Вы должны иметь возможность загрузить файл в виде двоичного файла (используя retrbinary) и использовать модуль codecs для преобразования из EBCDIC в любую желаемую выходную кодировку. Вы должны знать конкретную кодовую страницу EBCDIC, используемую в системе z/OS (например, cp500). Если файлы маленькие, вы можете даже сделать что-то вроде (для преобразования в 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()

Обновление: если вам нужно использовать retrlines для получения строк, и ваши строки возвращаются в правильной кодировке, ваш подход не будет работать, поскольку обратный вызов вызывается один раз для каждой строки. Таким образом, в обратном вызове sequence будет строкой, и ваш цикл for будет записывать отдельные символы в строке на выходе, каждый в отдельной строке. Таким образом, вы, вероятно, захотите выполнить цикл self.write(sequence + "\r\n"), а не цикл for. Тем не менее, по-прежнему не кажется особенно правильным создание подкласса file только для того, чтобы добавить этот служебный метод - вероятно, он должен быть в другом классе в вашей версии bells-and-whistles.

person Vinay Sajip    schedule 26.07.2009
comment
Спасибо, Винай, это интересная идея, но как мне вставить новые строки? (Это обычные zos PDS, а не файлы OpenEdition) - person Brent.Longborough; 26.07.2009
comment
Как тогда завершаются строки в хост-системе, если не с переводом строки EBCDIC? - person Vinay Sajip; 26.07.2009
comment
Файловая система хоста основана на записях. Это либо фиксированная длина, и в этом случае все записи имеют одинаковую длину, либо переменная длина, когда длина сохраняется в поле дескриптора в начале каждой записи. FTP.retrlines() правильно извлекает записи, но (я думаю, правильно) не предоставляет символы новой строки. - person Brent.Longborough; 26.07.2009
comment
@Vinay.Update: Ой, да, я понимаю. Когда я вернусь к мэйнфрейму, позже на этой неделе, я попробую некоторые идеи и отпишусь. - person Brent.Longborough; 26.07.2009

Ваш метод writelineswitheol добавляет '\r\n' вместо '\n', а затем записывает результат в файл, открытый в текстовом режиме. Эффект, независимо от того, на какой платформе вы работаете, будет нежелательным '\ r'. Просто добавьте '\n', и вы получите соответствующее окончание строки.

Надлежащая обработка ошибок не должна быть отнесена к версии «прибамбасов». Вы должны настроить свой обратный вызов так, чтобы ваш файл open() находился в try/except и сохранял ссылку на дескриптор выходного файла, ваш вызов записи находился в try/except, и у вас был метод callback_obj.close(), который вы используете, когда retrlines() явно возвращается к file_handle.close() (в попытке/за исключением) - таким образом вы получаете явную обработку ошибок, например сообщения «не могу (открыть | записать в | закрыть) файл X, потому что Y» И вам не нужно думать о том, когда ваши файлы будут неявно закрыты, и рискуете ли вы исчерпать дескрипторы файлов.

Python 3.x ftplib.FTP.retrlines() должен предоставить вам объекты str, которые являются строками Unicode, и вам нужно будет закодировать их, прежде чем писать их, если только кодировка по умолчанию не является latin1, что было бы довольно необычно для Windows. коробка. У вас должны быть тестовые файлы с (1) всеми возможными 256 байтами (2) всеми байтами, которые допустимы в ожидаемой кодовой странице EBCDIC.

[несколько «санитарных» замечаний]

  1. Вам следует подумать об обновлении Python с версии 3.0 (доказательная версия) до версии 3.1.

  2. Чтобы облегчить лучшее понимание вашего кода, используйте «i» в качестве идентификатора только в качестве индекса последовательности и только в том случае, если вы безвозвратно приобрели привычку из FORTRAN 3 или более десятилетий назад :-)

  3. Две проблемы, обнаруженные до сих пор (добавление конца строки к каждому символу, неправильный признак конца строки), обнаружились бы при первом тестировании.

person John Machin    schedule 27.07.2009
comment
Джон, спасибо. Будьте уверены, что я принял вашу справедливую критику к сведению. - person Brent.Longborough; 31.07.2009

Используйте повторные строки ftplib для загрузки файла из z/os, в каждой строке нет '\n'.

Это отличается от команды Windows ftp «получить ххх».

Мы можем переписать функцию «retrlines» в «retrlines_zos» в ftplib.py.

Просто скопируйте весь код retrlines и измените строку «обратный вызов» на:

...

обратный вызов (строка + \n)

...

Я проверил, и это сработало.

person chenwei86a    schedule 22.09.2020