将出东方 2023-02-21 17:32 采纳率: 80%
浏览 38
已结题

python搜索文本文件并分析导出内容结果出错

#遇到问题的现象描述:
用python编一个程序,用于搜索指定目录下的文本文件,并将其中包含主机信息的文本内容提取出来。
这些文本文件是用批处理生成的,批处理的内容是:
ipconfig /all >d:\任意名字.txt
如:

ipconfig /all >d:\1.txt

其生成的结果类似于:

Windows IP 配置

   主机名  . . . . . . . . . . . . . : MD-JK
   主 DNS 后缀 . . . . . . . . . . . : 
   节点类型  . . . . . . . . . . . . : 混合
   IP 路由已启用 . . . . . . . . . . : 
   WINS 代理已启用 . . . . . . . . . : 

以太网适配器 以太网:

   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Realtek PCIe GbE Family Controller
   物理地址. . . . . . . . . . . . . : 04-DE-C4-AE-34-27
   DHCP 已启用 . . . . . . . . . . . : 
   自动配置已启用. . . . . . . . . . : 
   本地链接 IPv6 地址. . . . . . . . : fe83::3143:bdfe:fc60:aea4%6(首选) 
   IPv4 地址 . . . . . . . . . . . . : 192.168.0.90(首选) 
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   获得租约的时间  . . . . . . . . . : 2022年11月22日 9:31:44
   租约过期的时间  . . . . . . . . . : 2159年3月25日 21:51:47
   默认网关. . . . . . . . . . . . . : 192.168.0.52
   DHCP 服务器 . . . . . . . . . . . : 192.168.0.52
   DHCPv6 IAID . . . . . . . . . . . : 100974928
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-25-AC-AE-0C-00-D4-C4-A1-8A-47
   DNS 服务器  . . . . . . . . . . . : 218.85.107.99
                                       218.85.102.95
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

用于获取各台电脑的:计算机名、IP地址及网卡号。
将收集来的文本文件统一放于d:\中,利用所编的python程序自动收集它们的如上三个参数,并逐一加入到一个字典中。以计算机名为键,IP及网卡号为值。

#问题相关代码片,运行结果,报错内容
以下是我编的代码,不好意思,我是初学者,故加了许多注释用于学习,请多包涵!

'''搜索指定目录下的文本文件,并将其中包含主机信息的文本内容提取出来'''
'''
思路:
1,先将指定目录下的文本文件含路径,加入一个列表
2,然后读取每一个文件,搜索是否包含“主机名”,
3,如果是,则将该文件(及路径)加入到另一个列表中
4,然后遍历该列表,将主机信息追加到小字典中
5,最后,以主机名为键,每个小字典为值,加入大字典中
'''

'''1,先将指定目录下的文本文件含路径,加入一个列表'''
#设置要获取文件列表的目录
import os
目录='d:\\'
#指定要获取的文件类型
文件类型='.txt'
#保存文件列表
文件列表=[]
路径文件列表=[]
for 文件名 in os.listdir(目录):
    #os.listdir(路径) 方法用于
    #返回指定的文件夹里包含的文件或文件夹的名字的列表
    路径文件=os.path.join(目录,文件名)
    #os.path.join(路径名1,路径名2,路径名n)函数用于
    #连接两个或更多的路径名
    #如果各路径名首字母不包含'/',则函数会自动加上
    #如果有一个路径名是一个绝对路径,则在它之前的所有路径名均会被舍弃
    #如果最后一个路径名为空,则生成的路径以一个'/'分隔符结尾
    if (os.path.isfile(路径文件) and 路径文件[-4:].lower()==文件类型):
        #os.path.isfile(路径文件):判断某一对象(需提供绝对路径)是否为文件
        #os.path.isdir(路径):判断某一对象(需提供绝对路径)是否为目录
        文件列表.append(文件名)
        路径文件列表.append(路径文件)
print(文件列表)
print(路径文件列表)

'''2,然后读取每一个文件,搜索是否包含“主机名”'''
'''3,如果是,则将该文件(及路径)加入到另一个列表中'''
查找主机='主机名'
主机列表=[]
for 文件名 in 路径文件列表:
    with open (文件名,'r',encoding='gbk',errors='ignore') as 文件:
        #r:只读
        #encoding='gbk':用gbk扩展码打开,以防乱码
        #errors='ignore':如果出错(连GBK都无法搞定的代码)就忽略
        for 行数 in 文件:
            行=文件.readline()
            #逐行读取文本文件
            if 查找主机 in 行:
                #如果在本行中找到关键字“主机名”
                主机列表.append(文件名)
                #则将路径文件追加到“主机列表”中
print(主机列表)

'''4,然后遍历该列表,将主机信息追加到小字典中'''
查找IP='IPv4 地址'
查找网卡='物理地址'
电脑字典={}
电脑字典汇总={}
for 主机文件 in 主机列表:
    with open (主机文件) as 主机信息:
        for 行数 in 主机信息:
            行=主机信息.readline()
            #逐行读取主机信息文件
            if 查找主机 in 行:
                索引=行.find(': ')+2
                #变量‘行’的内容其实是一个字符串,可以当作列表来处理
                #----------
                #find()方法:
                #find(要查找的字符串,开始索引默认0,结束索引默认-1末尾)
                #检测字符串中是否包含'要查找的字符串',
                #如果指定'开始索引'和'结束索引'范围,
                #则检查是否包含在指定范围内;
                #如果包含子字符串返回开始的索引值,否则返回-1
                #----------
                #由于搜索的关键词': '后有空格,故索引号+2
                主机=行[索引:-1]
                #将结果写入小字典,键=值
                #从“索引”号到本行末尾,都是主机名,例如:
                #主机名  . . . . . . . . . . . . . : JCDF
            elif 查找IP in 行:
                前索引=行.find(': ')+2
                后索引=行.find('(首选)')
                #因为这回要截取的是字符串的中间部分,而不是到末尾,例:
                #IPv4 地址 . . . . . . . . . . . . : 192.168.0.71(首选)
                IP=行[前索引:后索引]
                电脑字典[查找IP]=IP
            elif 查找网卡 in 行:
                索引=行.find(': ')+2
                网卡=行[索引:-1]
                电脑字典[查找网卡]=网卡
                #例:
                #物理地址. . . . . . . . . . . . . : E0-D3-5D-DE-7B-AB
        #读完一个文件
        '''5,最后,以主机名为键,每个小字典为值,加入大字典中'''
        #将小字典追加到大字典中
        电脑字典汇总[主机]=电脑字典
        print (f'退出for循环时的小字典:{电脑字典}')
        print (f'退出for循环时的大字典:{电脑字典汇总}')
    print (f'退出with open时的小字典:{电脑字典}')
    print (f'退出with open时的大字典:{电脑字典汇总}')
print (f'退出遍历文件时的小字典:{电脑字典}')
print (f'退出遍历文件时的大字典:{电脑字典汇总}')

运行的结果是:

>>> 
=============== RESTART: E:\python\文件_搜索并提取文件内容.py ===============
['1.txt', '2.txt', '3.txt', '4.txt', '5.txt', 'LogFile_Tcard.txt', 'ROBOCOPY_HELP.TXT']
['d:\\1.txt', 'd:\\2.txt', 'd:\\3.txt', 'd:\\4.txt', 'd:\\5.txt', 'd:\\LogFile_Tcard.txt', 'd:\\ROBOCOPY_HELP.TXT']
['d:\\1.txt', 'd:\\5.txt']
退出for循环时的小字典:{'物理地址': 'E0-D3-5D-DE-7B-AB', 'IPv4 地址': '192.168.0.71'}
退出for循环时的大字典:{'JCDF': {'物理地址': 'E0-D3-5D-DE-7B-AB', 'IPv4 地址': '192.168.0.71'}}
退出with open时的小字典:{'物理地址': 'E0-D3-5D-DE-7B-AB', 'IPv4 地址': '192.168.0.71'}
退出with open时的大字典:{'JCDF': {'物理地址': 'E0-D3-5D-DE-7B-AB', 'IPv4 地址': '192.168.0.71'}}
退出for循环时的小字典:{'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}
退出for循环时的大字典:{'JCDF': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}, 'MD-JK': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}}
退出with open时的小字典:{'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}
退出with open时的大字典:{'JCDF': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}, 'MD-JK': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}}
退出遍历文件时的小字典:{'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}
退出遍历文件时的大字典:{'JCDF': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}, 'MD-JK': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}}

可以看出,从第二个文本文件读取的内容覆盖到了第一个文件的分析结果中({'物理地址': 'E0-D3-5D-DE-7B-AB', 'IPv4 地址': '192.168.0.71'}被{'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}覆盖了。并且这个BUG是在退出里层for循环时就出现的。

#我期待的结果是:

退出遍历文件时的大字典:{'JCDF': {'物理地址': 'E0-D3-5D-DE-7B-AB', 'IPv4 地址': '192.168.0.71'}, 'MD-JK': {'物理地址': '04-D4-C4-AE-8A-47', 'IPv4 地址': '192.168.0.90'}}

请教各位,如何修改代码?不胜感激!

  • 写回答

2条回答 默认 最新

  • 将出东方 2023-03-01 12:07
    关注

    由于没人回答,经不懈努力,终于成功解决了以上问题,虽然还很不成熟,希望能抛砖引玉。
    主要改进是:放弃了字典存储模式,用理简单的列表存储结果,以简化逻辑。
    本人是Python的初学者,以学为目的,主要用于解决现实工作中的问题,故添加了大量的注解给自己看,还请各位海涵。

    '''
    在指定目录下,有从局域网各台电脑中收集来的文本文件,内容是由如下DOS命令生成的:
    ipconfig /all >d:\任意名.txt
    本程序的任务是:收集并整理这些文本文件,提取其中的主机名、IP、MAC号,生成局域网电脑IP-MAC绑定列表(用于DHCP)
    '''
    '''
    思路:
    1,获取指定目录下所有文本文件的路径及文件名,生成路径文件列表
    2,按路径文件列表,逐一打开这些文件,遍历全文,
    检查其中是否包含“主机名”:如果是,加入到计算机列表中。
    3,按计算机列表,逐一打开这些文件,遍历全文,搜索‘主机名’、
    'IPv4 地址'、'物理地址',每个文件存储成一条列表
    4,将所有列表加入到一个大列表中
    5,输出到文本文件
    6,打开该文本文件,删除所有不必要的[],'等字符,‘主机名’、
    'IPv4 地址'、'物理地址'之间以空格分隔,不同主机信息各占一行
    '''
    
    '''1,获取指定目录下所有文本文件的路径及文件名,生成路径文件列表'''
    # 设置要获取文件列表的目录
    import os
    
    目录 = 'd:\\'
    # 指定要获取的文件类型
    文件类型 = '.txt'
    # 保存路径文件列表
    路径文件列表 = []
    for 文件名 in os.listdir(目录):
        # os.listdir(路径) 方法用于
        # 返回指定的文件夹里包含的文件或文件夹的名字的列表
        路径文件 = os.path.join(目录, 文件名)
        # os.path.join(路径名1,路径名2,路径名n)函数用于
        # 连接两个或更多的路径名
        # 如果各路径名首字母不包含'/',则函数会自动加上
        # 如果有一个路径名是一个绝对路径,则在它之前的所有路径名均会被舍弃
        # 如果最后一个路径名为空,则生成的路径以一个'/'分隔符结尾
        if (os.path.isfile(路径文件) and 路径文件[-4:].lower() == 文件类型):
            # os.path.isfile(路径文件):判断某一对象(需提供绝对路径)是否为文件
            # 拓展:os.path.isdir(路径):判断某一对象(需提供绝对路径)是否为目录
            # 路径文件[-4:]:拓展名.txt是从索引号-4到字符串末尾
            路径文件列表.append(路径文件)
    print(路径文件列表)
    
    '''
    2,按路径文件列表,逐一打开这些文件,遍历全文,
            检查其中是否包含“主机名”:如果是,加入到计算机列表中
    '''
    查找主机 = '主机名'
    计算机列表 = []
    for 目录 in 路径文件列表:
        with open(目录, 'r', encoding='utf-8', errors='ignore') as 文件:
            '''
            这里需要预先对所有收集来的文本文件进行预处理,转码成为utf-8
            可以用UltraEdit的程序员环境,左侧的资源管理器里找到文本文件的
            存放目录,按shift选择要转码的文件,菜单:文件→转换→
            ASCII转UTF-8(UniCode编辑)
            '''
            # errors='ignore'代表如果不能用encoding指定的编码格式打开,就忽略它
            文件内容 = 文件.read()
            # read()方法一次性读取文件所有内容到变量“文件内容”中
            if 查找主机 in 文件内容:
                计算机列表.append(目录)
    print(计算机列表)
    
    '''
    3,按计算机列表,逐一打开这些文件,遍历全文,搜索‘主机名’、
    'IPv4 地址'、'物理地址',每个文件存储成一条列表
    '''
    查找IP = 'IPv4 地址'
    查找网卡 = '物理地址'
    临时电脑信息列表 = []
    局域网电脑列表 = []
    for 目录 in 计算机列表:
        with open(目录, 'r', encoding='utf-8', errors='ignore') as 文件:
            for 行数 in 文件:
                行内容 = 文件.readline()
                if 查找主机 in 行内容:
                    索引 = 行内容.find(': ') + 2
                    # 变量‘行’的内容其实是一个字符串,可以当作列表来处理
                    # 由于搜索的关键词': '后有空格,故索引号+2
                    '''
                    find()方法:
                    find(要查找的字符串,开始索引默认0,结束索引默认-1末尾)
                    检测字符串中是否包含'要查找的字符串',
                    如果指定'开始索引'和'结束索引'范围,
                    则检查是否包含在指定范围内;
                    如果包含子字符串返回开始的索引值,否则返回-1
                    '''
                    临时电脑信息列表.append(行内容[索引:-1])
                elif 查找网卡 in 行内容:
                    索引 = 行内容.find(': ') + 2
                    网卡 = 行内容[索引:-1]
                    临时电脑信息列表.append(网卡)
                    # 例:物理地址. . . . . . . . . . . . . : E0-D5-5E-D6-7A-CA
                elif 查找IP in 行内容:
                    前索引 = 行内容.find(': ') + 2
                    后索引 = 行内容.find('(首选)')
                    # print (行内容[前索引:后索引])
                    临时电脑信息列表.append(行内容[前索引:后索引])
                    # 因为这回要截取的是字符串的中间部分,而不是到末尾,例:
                    # IPv4 地址 . . . . . . . . . . . . : 192.168.0.21(首选)
            '''4,将所有列表加入到一个大列表中'''
            局域网电脑列表.append(临时电脑信息列表)
            临时电脑信息列表 = []
            # 清空临时电脑信息列表
    
    '''5,输出到文本文件'''
    import sys
    
    # 设置要删除的标点符号列表,不能直接用"for 标点 in string.punctuation:"
    # 因为string.punctuation包含了'.'与‘-’号,而IP与网卡号需要保留它们
    要删除的符号 = ["[", "]", ",", "'"]
    # 调入系统模块
    写入文件路径 = 'd:\\临时转存列表.txt'
    with open(写入文件路径, 'w', encoding='utf-8') as 文件:
        sys.stdout = 文件
        # .stdout:标准输出,将下面的打印内容输出到文本文件中
        for 变量 in 局域网电脑列表:
            print(变量)
    '''
    6,打开该文本文件,删除所有不必要的[], '等字符,‘主机名’、
        'IPv4 地址'、'物理地址'
        之间以空格分隔,不同主机信息各占一行
    '''
    最终结果文件 = 'd:\\局域网电脑列表.txt'
    # 设置要删除和标点符号列表
    要删除的符号 = ["[", "]", ",", "'"]
    # 不能直接用'for 标点 in string.punctuation:'
    # 因为string.punctuation包含‘.’和‘-’,IP和MAC需要保留它们
    with open(写入文件路径, 'a+', encoding='utf-8') as 文件:
        sys.stdout = 文件
        '''
        执行sys.stdout=文件之后,对“文件”的任何操作也将对sys.stdout执行。
        离开with块会关闭“文件”,因此它也会关闭sys.stdout。然后,当您稍后
        执行print时,它尝试将此写入sys.stdout,但它已关闭,因此会得到一
        个错误:ValueError: I/O operation on closed file。
        所以打开第二个文件后,需要再次使用sys.stdout=文件
        '''
        with open(最终结果文件, 'w', encoding='utf-8') as 结果:
            '''
            多了这步with open实属无奈,找不到python打开文件直接编辑的功能
            只能新开一个文件,将从原文件中读到的内容经修改后,写到新文件
            各位若有好办法,还望赐教
            '''
            sys.stdout = 结果
            文件.seek(0)
            '''
            因为上面的“with open(写入文件路径,'a+',encoding='utf-8')as 文件:”
            中使用的是“a+”,以读写模式打开文件,指针在文件尾。故要将其
            复位到文件头,否则下面的read()方法读不到内容
            '''
            内容 = 文件.read()
            # 将从原文件读到的内容进行修改(替换标点)并写入新文件
            for 标点 in 要删除的符号:
                内容 = 内容.replace(标点, '')
            print(内容)
    
    # 删除临时文件
    os.remove(写入文件路径)
    # 打开目录
    os.startfile('d:\\')
    # 用操作系统默认程序打开上述文件
    os.startfile(最终结果文件)
    
    '''写在后面的话
    用表格处理软件(如EXCEL)导入d:\局域网电脑列表.txt文件时,以空格
    为间隔标志,各行的导入类型设为“文本”,以防又出现科学计数法
    因我是初学者,以学习为目的,受限于个人水平,这段代码反复遍历、
    反复打开文件,反复写文件,这些效率都很低下,肯定有很大的改善空间
    各位若有更高效的方法,还望不吝赐教
    '''
    

    最终输出结果是:

    XCT E0-D5-5E-06-7A-0A 192.168.0.121
    WIN-NF600SV30CO DC-F0-01-E7-02-90 192.168.1.95 DC-0-01-E7-02-91 DC-F0-01-E7-A2-03
    WIN-4KNB0DUN03F 00-F0-4D-00-00-A6 10.127.127.1 80-18-44-EB-34-06 80-18-44-EB-34-04 192.168.1.106 80-18-04-E0-34-05 00-00-00-00-00-00-00-E0
    MD-JK 04-D4-C0-AE-8A-07 192.168.0.190
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

问题事件

  • 系统已结题 3月9日
  • 已采纳回答 3月1日
  • 创建了问题 2月21日

悬赏问题

  • ¥15 程序不包含适用于入口点的静态Main方法
  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
  • ¥15 ubuntu子系统密码忘记