使用嵌套列表和自定义键值对将字符串转换为嵌套字典

Convert string to nested dictionary with nested list and custom key-value pairs

我正在从网络交换机中提取数据,它以这样的字符串形式输出。

Gi1/0/1   COMPUTER1  Full   1000    Auto Down   off   A  (1),5-7777
Gi1/0/2   COMPUTER2  Full   1000    Auto Down   On    T  (1),5-7777
Gi1/0/3   COMPUTER3  Full   1000    Auto Up     Off   A  (1),5-7777
Gi1/0/4   COMPUTER4  Full   1000    Auto Down   Off   A  (1),5-7777
Gi1/0/5   COMPUTER5  Full   1000    Auto Down   Off   A  1
Gi1/0/6   COMPUTER6  Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/7   COMPUTER7  N/A    Unknown Auto Down   Off   A  1
Gi1/0/8   COMPUTER8  Full   1000    Auto Up     Off   A  1
Gi1/0/9   COMPUTER9  Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/10  COMPUTER10 Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/11  COMPUTER11 Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/12  COMPUTER12 Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/13  COMPUTER13 Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/14  COMPUTER14 Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/15  Server1    N/A    Unknown Auto Down   Off   A  55
Gi1/0/16  Server2    N/A    Unknown Auto Down   Off
Gi1/0/17  Server3    N/A    Unknown Auto Down   Off
Gi1/0/18  Server4    N/A    Unknown Auto Down   Off
Gi1/0/19  Server5    Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/20  Server6    Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/21  Server7    Full   1000    Auto Up     On    T  (1),5-7777
Gi1/0/22  Server8    Full   1000    Auto Up     On    A  3311
Gi1/0/23  COMPUTER15 Full   1000    Auto Up     Off   A  25
Gi1/0/24  COMPUTER16 Full   1000    Auto Up     On    A  99
Gi1/0/25  COMPUTER17 Full   1000    Auto Up     On    A  99
Gi1/0/26  Server9    Full   10      Auto Up     On    A  99
Gi1/0/27  COMPUTER18 Full   10      Auto Up     On    A  99
Gi1/0/28             N/A    Unknown Auto Down   Off   A  1
Gi1/0/29             N/A    Unknown Auto Down   Off   A  1
Gi1/0/30             N/A    Unknown Auto Down   Off   A  1
Gi1/0/31             N/A    Unknown Auto Down   Off   A  1
Gi1/0/32             N/A    Unknown Auto Down   Off   A  1
Gi1/0/33             N/A    Unknown Auto Down   Off   A  1
Gi1/0/34             N/A    Unknown Auto Down   Off   A  1
Gi1/0/35             N/A    Unknown Auto Down   Off   A  1
Gi1/0/36             N/A    Unknown Auto Down   Off   A  1
Gi1/0/37             N/A    Unknown Auto Down   Off   A  1
Gi1/0/38             N/A    Unknown Auto Down   Off   A  1
Gi1/0/39             N/A    Unknown Auto Down   Off   A  1
Gi1/0/40             N/A    Unknown Auto Down   Off   A  1
Gi1/0/41             N/A    Unknown Auto Down   Off   A  1
Gi1/0/42             N/A    Unknown Auto Down   Off   A  1
Gi1/0/43             N/A    Unknown Auto Down   Off   A  1
Gi1/0/44             N/A    Unknown Auto Down   Off   A  1
Gi1/0/45             N/A    Unknown Auto Down   Off   A  1
Gi1/0/46             N/A    Unknown Auto Down   Off   A  1
Gi1/0/47             N/A    Unknown Auto D-Down Off   A  1
Gi1/0/48             N/A    Unknown Auto Down   Off   A  1

我正在将 TextFSM 与 Netmiko 结合使用,但我想知道如何在不使用 TextFSM 的情况下格式化数据。

我想将数据转换成我可以像这样解析的地方:

print(port[14]['Description'])

我会得到 COMPUTER14

我认为结构应该是这样的:

{port: {14: {
               'Interface': 'Gi1/0/14',
               'Description': 'COMPUTER14',
               'Duplex': 'Full',
               'Speed': '1000',
               'Neg': 'Auto',
               'Linkstate': 'Up',
               'Flowctrl': 'On',
               'M': 'T'
               'VLAN': ['(1)', '5-7777']
               },
          15: {
               'Interface': 'Gi1/0/15',
               'Description': 'SERVER1',
               'Duplex': 'N/A',
               'Speed': 'Unknown',
               'Neg': 'Auto',
               'Linkstate': 'Down',
               'Flowctrl': 'off',
               'M': 'A',
               'VLAN': [55]
               }
         }
}
# VLAN would be a list and anything that doesn't have data would return 'None'

但不确定如何使用 Python 来解决这个问题。我能做的最多就是使用 splitlines().

转换为列表

编辑: 在我尝试做之前:

data_list = output.splitlines()

 for data in data_list:
      print(data.split(' '))

但是从那里出来的列表是这样的:

['Gi1/0/1', '', '', 'COMPUTER1', 'Full', '', '', '1000', '', '', '', 'Auto', 'Down', '', '', '', '', 'off', '', '', '', 'A', '', '(1),5-7777']

从这里我看到我需要将列表变成字典,但我不知道如何计算空格以及没有数据的空格,我仍然想证明有none.

那个数据出来是这样的:

['Gi1/0/28', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'N/A', '', '', '', 'Unknown', 'Auto', 'Down', '', '', 'Off', '', '', 'A', '', '1']

前进我累了:

keys = ["Interface", "Description", "Duplex", "Speed", "Neg", "Linkstate", "Flowctrl", "M", "VLAN"]

for data in data_list:
    z = zip(keys, data.split(' '))
    dictionary = dict(z)
    print(dictionary)

尽管这构成了一个字典,但由于空格,键与值无法正确匹配。

{'Interface': 'Gi1/0/1', 'Description': '', 'Duplex': '', 'Speed': 'COMPUTER1', 'Neg': 'Full', 'Linkstate': '', 'Flowctrl': '', 'M': '1000', 'VLAN': ''}
{'Interface': 'Gi1/0/28', 'Description': '', 'Duplex': '', 'Speed': '', 'Neg': '', 'Linkstate': '', 'Flowctrl': '', 'M': '', 'VLAN': ''}

如何计算空格,或者我走错方向了?

从聊天中的对话来看,文本解析似乎是唯一的出路。我复制了整个文本并将其保存到一个文件中,因为我假设您已将命令的输出存储在一个文件中。不过,为了回答的长度,我只尝试使用 运行 几个端口而不是全部 48 个端口。另外 请注意,这仅在每一列至少有一行数据时才有效。如果有一列没有端口有任何数据

我没有使用 readline,而是使用了 read(),这样我就可以在 \n 处拆分它。当使用 readlines()

时,这基本上删除了行中每个条目末尾的 \n
with open('port_data.txt', 'r') as file:
    contents = file.read()
lines = contents.split('\n')
noOfLine = len(lines)

我们还维护了一个列描述列表,我相信将来不会为相关命令更改。

columns = ['Interfaces', 'Description', 'Duplex', 'Speed', 'Neg', 'LinkState', 'Flow Control', 'M', 'VLAN']
colSize = len(columns)

我所做的是为每列内容保留一个列表,以便轻松迭代以获取所有数据。

# This is a list of lists. 
# finalList[0] is a list of all ports
# finalList[1] is a list of all descriptions, etc.
# finalList will have 9 lists because we have 9 columns here.
finalList = [] 

# This is the output dictionary
output = {}

为了构建字典,我们将 lines[] 中的每一行拆分为 ' '。这将确保 split() 生成的列表的第一个索引具有您需要的数据。获得该数据后,我们需要找出下一列的起始位置。

我们知道下一列从该列中最长条目之后的 sspaces 处开始。比如row1的名字是apple,row2的名字是photosynthesis,那么我们就知道row1的第一个词后面多了space来容纳row2同一列的长词。为了找到这个大小,我们跟踪变量中列的 最大长度 。我们对每一列都这样做。

for i in range(len(columns)): # Perform the loop for every column
    resultList = [] # This is used to keep the list of every entry in the current column
    maxSize = 0 # This is the max length of an entry in the current column. We compute this at run time.
    for line in lines:
        line = line.rstrip() # Remove any trailing spaces in the right end.
        curString = line.split(' ')[0] # Split and get the first word
        maxSize = max(maxSize, len(curString)) # Update the maxSize
        resultList.append(curString) # Add this word to the current column list.
    finalList.append(resultList) # Add the current column to the list of columns we already created.

现在我们有了当前列中最大单词的长度,我们可以确定对于每一行,下一列出现在 maxSize 之后的某个点以适应间距。对于下一列非空的行,下一个单词可以在 space 秒后开始。但是,对于下一列为空条目的行,会有更多的 spaces.

为了适应这一点,我们找到在当前 maxSize 之后 space 数量最少的行,并且 trim 行列表中的所有字符串从该位置开始。在同一个主循环中

newLineList = [] # This is going to be the list of lines after we have removed the first entry
minLength = 10000 # Arbitrarily large number
if i != colSize-1: # We don't have to do this for the last column because we won't be processing it anymore.
    for line in lines: # Each line
        minLength = min(minLength, len(line[maxSize:])-len(line[maxSize:].lstrip())) # len('    Apple') - len('Apple') = 4 meaning there are 4 spaces from the current position of line[maxSize]
    # Now that we have the minimum length, we can say for sure that the next column starts at maxSize+minLength for every row.
    for line in lines:
        newLineList.append(line[maxSize+minLength:]) # temporary holder for recomputed lines.
    lines = [] # Set it to empty
    for line in newLineList:
        lines.append(line) # Append all the new lines to the original lines set for next iteration.

现在我们应该有一个这样的列表:

>>> for x in finalList:
>>> ... print(x)
['Gi1/0/1', 'Gi1/0/2', 'Gi1/0/3', 'Gi1/0/4', 'Gi1/0/47', 'Gi1/0/48']
['COMPUTER1', 'COMPUTER2', 'COMPUTER3', 'COMPUTER4', '', '']
['Full', 'Full', 'Full', 'Full', 'N/A', 'N/A']
['1000', '1000', '1000', '1000', 'Unknown', 'Unknown']
['Auto', 'Auto', 'Auto', 'Auto', 'Auto', 'Auto']
['Down', 'Down', 'Up', 'Down', 'D-Down', 'Down']
['off', 'On', 'Off', 'Off', 'Off', 'Off']
['A', 'T', 'A', 'A', 'A', 'A']
['(1),5-7777', '(1),5-7777', '(1),5-7777', '(1),5-7777', '1', '1']

既然我们已经将每一列单独放在一个列表中,那么构建字典就不那么困难了。

for i in range(noOfLine): # For each row
    result = {} # This is a new entry in the final JSON of ports.
    for j in range(colSize): # For each column
        if(columns[j] == 'VLAN'): # For VLAN, we need to perform a split to get list output
            result[columns[j]] = finalList[j][i].split(',') # If the VLAN is a single entry, we would just get that entry in a list.
        else:
            result[columns[j]] = finalList[j][i].strip() # Removes any excessive spaces.
    name = finalList[0][i] # Get the port name like Gi1/0/1 or Gi1/0/2

    output[name.split('/')[-1]] = result # Compute the actual port number. Split at '/' gives ['Gi1','0','1'] from which we take a last entry as the port number.

完整代码放在一起:

import pprint

with open('Data_to_Csv.txt', 'r') as file:
    contents = file.read()

lines = contents.split('\n')
noOfLine = len(lines)
columns = ['Interfaces', 'Description', 'Duplex', 'Speed', 'Neg', 'LinkState', 'Flow Control', 'M', 'VLAN']
colSize = len(columns)
finalList = []
output = {}

for i in range(len(columns)):
    resultList = []
    newLineList = []
    maxSize = 0
    for line in lines:
        line = line.rstrip()
        curString = line.split(' ')[0]
        maxSize = max(maxSize, len(curString))
        resultList.append(curString)
    finalList.append(resultList)
    minLength = 10000
    if i != colSize-1:
        for line in lines:
            minLength = min(minLength, len(line[maxSize:])-len(line[maxSize:].lstrip()))
        for line in lines:
            newLineList.append(line[maxSize+minLength:])
        lines = []
        for line in newLineList:
            lines.append(line)

for x in finalList:
    print(x)

for i in range(noOfLine):
    result = {}
    for j in range(colSize):
        # print("J = {}".format(j))
        if(columns[j] == 'VLAN'):
            result[columns[j]] = finalList[j][i].split(',')
        else:
            result[columns[j]] = finalList[j][i].strip()
        # print("Columns = {}".format(columns[j]))
        # print("result = {}".format(result[columns[j]]))
    name = finalList[0][i]

    output[name.split('/')[-1]] = result
pprint.pprint(output)

以上代码的输出:

{'1': {'Description': 'COMPUTER1',
       'Duplex': 'Full',
       'Flow Control': 'off',
       'Interfaces': 'Gi1/0/1',
       'LinkState': 'Down',
       'M': 'A',
       'Neg': 'Auto',
       'Speed': '1000',
       'VLAN': ['(1)', '5-7777']},
 '2': {'Description': 'COMPUTER2',
       'Duplex': 'Full',
       'Flow Control': 'On',
       'Interfaces': 'Gi1/0/2',
       'LinkState': 'Down',
       'M': 'T',
       'Neg': 'Auto',
       'Speed': '1000',
       'VLAN': ['(1)', '5-7777']},
 '3': {'Description': 'COMPUTER3',
       'Duplex': 'Full',
       'Flow Control': 'Off',
       'Interfaces': 'Gi1/0/3',
       'LinkState': 'Up',
       'M': 'A',
       'Neg': 'Auto',
       'Speed': '1000',
       'VLAN': ['(1)', '5-7777']},
 '4': {'Description': 'COMPUTER4',
       'Duplex': 'Full',
       'Flow Control': 'Off',
       'Interfaces': 'Gi1/0/4',
       'LinkState': 'Down',
       'M': 'A',
       'Neg': 'Auto',
       'Speed': '1000',
       'VLAN': ['(1)', '5-7777']},
 '47': {'Description': '',
        'Duplex': 'N/A',
        'Flow Control': 'Off',
        'Interfaces': 'Gi1/0/47',
        'LinkState': 'D-Down',
        'M': 'A',
        'Neg': 'Auto',
        'Speed': 'Unknown',
        'VLAN': ['1']},
 '48': {'Description': '',
        'Duplex': 'N/A',
        'Flow Control': 'Off',
        'Interfaces': 'Gi1/0/48',
        'LinkState': 'Down',
        'M': 'A',
        'Neg': 'Auto',
        'Speed': 'Unknown',
        'VLAN': ['1']}}