6

我正在尝试使用 Python 和 ftplib 自动从 z/os PDS 下载一些文本文件。

由于主机文件是 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,平台是 Windows XP 下的 MingW。

z/os PDS 具有固定的记录结构,而不是依赖行结尾作为记录分隔符。但是,z/os FTP 服务器在以文本模式传输时,会提供记录结尾,而 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()

感谢 John 和 Vinay

4

5 回答 5

5

刚刚遇到这个问题,因为我试图弄清楚如何从 z/OS 递归下载数据集。多年来,我一直在使用简单的 python 脚本从大型机下载 ebcdic 文件。它有效地做到了这一点:

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

file = open(filename, "w")
ftp.retrlines("retr " + filename, writeline)
于 2011-11-01T10:13:45.453 回答
3

您应该能够将文件下载为二进制文件(使用retrbinary)并使用codecs模块将 EBCDIC 转换为您想要的任何输出编码。您应该知道在 z/OS 系统上使用的特定 EBCDIC 代码页(例如 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 循环会将行中的单个字符写入输出,每个字符都在自己的 line 上。所以你可能想要做self.write(sequence + "\r\n")而不是for循环。但是,仅仅为了添加这个实用方法而进行子类化仍然感觉不是特别正确file- 它可能需要在您的bells-and-whistles版本中位于不同的类中。

于 2009-07-26T15:39:56.220 回答
1

您的 writelineswitheol 方法附加 '\r\n' 而不是 '\n',然后将结果写入以文本模式打开的文件。无论您在什么平台上运行,效果都将是一个不需要的 '\r'。只需附加'\n',您将获得适当的行尾。

正确的错误处理不应归结为“花里胡哨”的版本。您应该设置回调,以便您的文件 open() 在 try/except 中并保留对输出文件句柄的引用,您的 write 调用在 try/except 中,并且您有一个 callback_obj.close() 方法当 retrlines() 返回显式 file_handle.close() 时使用(在 try/except 中)——这样你会得到显式错误处理,例如消息“不能(打开|写入|关闭)文件 X 因为 Y”和您不必考虑文件何时将被隐式关闭以及是否有文件句柄用完的风险。

Python 3.x ftplib.FTP.retrlines() 应该为您提供实际上是 Unicode 字符串的 str 对象,您需要在编写它们之前对其进行编码——除非默认编码是 latin1,这对于 Windows 来说是相当不寻常的盒子。您应该拥有包含 (1) 所有可能的 256 字节 (2) 在预期 EBCDIC 代码页中有效的所有字节的测试文件。

【几句“卫生”的话】

  1. 您应该考虑将 Python 从 3.0(“概念证明”版本)升级到 3.1。

  2. 为了便于更好地理解您的代码,请使用“i”作为标识符,仅作为序列索引,并且仅当您在 3 年或更长时间前从 FORTRAN 获得了不可挽回的习惯 :-)

  3. 到目前为止发现的两个问题(将行终止符附加到每个字符,错误的行终止符)会在您第一次测试时出现。

于 2009-07-27T00:54:17.900 回答
0

使用ftplib 的retrlines 从z/os 下载文件,每一行没有'\n'。

它不同于windows ftp 命令'get xxx'。

我们可以在 ftplib.py 中将函数 'retrlines' 重写为 'retrlines_zos'。

只需复制 retrlines 的整个代码,并将“回调”行更改为:

...

回调(行+“\n”)

...

我测试了它并且有效。

于 2020-09-22T07:08:34.843 回答
0

你想要一个 lambda 函数和一个回调。像这样:

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

ftpcommand = "RETR {}{}{}".format("'",zOsFile,"'")  
filename = "newfilename"
with open( filename, 'w' ) as file :
     callback_lambda = lambda x: writeLineCallback(x,file)
     ftp.retrlines(ftpcommand, callback_lambda)

这将下载文件'zOsFile'并将其写入'newfilename'

于 2021-12-21T22:27:10.663 回答