SkillAgentSearch skills...

Shconfparser

It's a Network configuration parser, which translates the show outputs of cisco and other vendors. Show config parser is unique tool of translates show commands into tree, table, data formats

Install / Use

/learn @network-tools/Shconfparser

README

Show Configuration Parser (shconfparser)

License: MIT Tests codecov Downloads GitHub issues open CodeQL PyPI

🚀 Version 3.0 - Modern Python library (3.8+) with uv support! See docs/ for guides.

Introduction

Show configuration parser (shconfparser) is a Python library for parsing network device configurations. This library examines the config and breaks it into a set of parent and clild relationships.

shconfparser is a vendor independent library where you can parse the following formats:

  • Tree structure i.e. show running
  • Table structure i.e. show ip interface
  • Data i.e. show version

Modern Format (JSON/YAML) - Hierarchical Structure

show run to modern YAML format structure <br/> <br/> show run to modern JSON format structure

Legacy Format - OrderedDict with Full Keys

show run to legacy format structure

Table Structure

show cdp neighbour to table structure

Key Features

Zero Dependencies - Uses only Python standard library
Fast - Modern tooling with uv package manager support
🔒 Type Safe - Full type hints and py.typed marker
🎯 Vendor Independent - Works with any network device configuration
📊 Multiple Formats - Parse trees, tables, and unstructured data
📄 Format Flexibility - Output as JSON or YAML structures
🔍 XPath Queries - NSO-style queries with context tracking (NEW!)
🧪 Well Tested - 80%+ code coverage, tested on Python 3.8-3.13

Quick Start

Installation

pip install shconfparser

Faster with uv:

curl -LsSf https://astral.sh/uv/install.sh | sh
uv pip install shconfparser

Basic Usage

Modern format (recommended - hierarchical structure with XPath):

from shconfparser.parser import Parser

# Use modern format for cleaner output and XPath support
p = Parser(output_format='json')  # or 'yaml'
data = p.read('running_config.txt')

# Parse directly (no split needed for single show running command)
tree = p.parse_tree(data)
print(p.dump(tree, indent=2))

# Query with XPath
result = p.xpath('/hostname')
print(result.data)  # 'R1'
<details> <summary>Alternative: Legacy format (backward compatible)</summary>
p = Parser()  # Defaults to 'legacy' format
# or explicitly: Parser(output_format='legacy')
data = p.read('running_config.txt')
tree = p.parse_tree(data)
print(p.dump(tree, indent=4))
# Returns OrderedDict with full command strings as keys
# Example: {'interface FastEthernet0/0': {...}}
</details>

Multiple show commands in one file:

from shconfparser.parser import Parser

p = Parser(output_format='json')  # Modern format recommended
data = p.read('multiple_commands.txt')  # Contains multiple show outputs
data = p.split(data)  # Split into separate commands
data.keys()
# odict_keys(['running', 'version', 'cdp_neighbors', 'ip_interface_brief'])

# Now parse each command separately
data['running'] = p.parse_tree(data['running'])

headers = ['Device ID', 'Local Intrfce', 'Holdtme', 'Capability', 'Platform', 'Port ID']
data['cdp_neighbors'] = p.parse_table(data['cdp_neighbors'], header_names=headers)

print(p.dump(data['running'], indent=2))
<details> <summary>Alternative: Access internal properties</summary>
p = Parser()
p.read('multiple_commands.txt')
p.split(p.r.data)

# Access split data from internal property
data = p.s.shcmd_dict
data['running'] = p.parse_tree(data['running'])
print(p.dump(data['running'], indent=4))
</details>

Usage Examples

Check Library Version

import shconfparser
print(shconfparser.__version__)  # '3.0.0'

Parse Tree Structure (show running-config)

from shconfparser.parser import Parser

p = Parser()

# Single command file - parse directly
data = p.read('running_config.txt')
tree = p.parse_tree(data)  # No split() needed

# Access nested configuration
print(p.dump(tree['interface FastEthernet0/0'], indent=2))
# {
#   "ip address 1.1.1.1 255.255.255.0": null,
#   "duplex auto": null,
#   "speed auto": null
# }

Parse Table Structure (show cdp neighbors)

# Single command file
p = Parser()
data = p.read('cdp_neighbors.txt')

# Parse table directly (no split needed)
headers = ['Device ID', 'Local Intrfce', 'Holdtme', 'Capability', 'Platform', 'Port ID']
cdp_data = p.parse_table(data, header_names=headers)

# Access as list of dictionaries
for neighbor in cdp_data:
    print(f"{neighbor['Device ID']} on {neighbor['Local Intrfce']}")
# Output: R2 on Fas 0/0

Parse Unstructured Data (show version)

# Single command file
p = Parser()
data = p.read('show_version.txt')

# Parse show version output directly
version_data = p.parse_data(data)  # No split() needed

# Search for specific information
import re
for line in version_data.keys():
    if re.search(r'IOS.*Version', line):
        print(line)
# Output: Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d)...

Search in Tree

# Search for all interfaces
pattern = r'interface\s+\w+.*'
matches = p.search.search_all_in_tree(pattern, tree)

for key, value in matches.items():
    print(value)
# interface FastEthernet0/0
# interface FastEthernet0/1

Search in Table

# Find specific device in CDP table
pattern = r'R\d+'
match = p.search.search_in_table(pattern, cdp_data, 'Device ID')
print(match)
# {'Device ID': 'R2', 'Local Intrfce': 'Fas 0/0', ...}

Output Format Selection

Parse configurations in legacy (OrderedDict) or modern (dict) hierarchical structures:

from shconfparser.parser import Parser

# Legacy format (backward compatible - OrderedDict with full keys)
p = Parser()  # Defaults to 'legacy'
# or explicitly: Parser(output_format='legacy')
data = p.read('running_config.txt')
tree = p.parse_tree(data)  # Returns OrderedDict
print(type(tree))  # <class 'collections.OrderedDict'>
# Example: {'interface FastEthernet0/0': {'ip address 1.1.1.1': ''}}

# Modern formats: JSON or YAML (hierarchical dict structure)
p = Parser(output_format='json')  # Hierarchical dict
# or: Parser(output_format='yaml')  # Same structure, different name
data = p.read('running_config.txt')
tree = p.parse_tree(data)  # Returns dict
print(type(tree))  # <class 'dict'>
# Example: {'interface': {'FastEthernet0/0': {'ip': {'address': '1.1.1.1'}}}}

# Override format per call
p = Parser()  # Legacy by default
tree_legacy = p.parse_tree(data)  # OrderedDict
tree_json = p.parse_tree(data, format='json')  # dict

Format Comparison:

# Legacy format - preserves exact CLI structure (OrderedDict)
{
    "interface FastEthernet0/0": {
        "ip address 1.1.1.1 255.255.255.0": "",
        "duplex auto": ""
    }
}

# Modern formats (json/yaml) - hierarchical and programmatic (dict)
{
    "interface": {
        "FastEthernet0/0": {
            "ip": {
                "address": "1.1.1.1 255.255.255.0"
            },
            "duplex": "auto"
        }
    }
}

Benefits of modern formats (json/yaml):

  • Cleaner hierarchy for nested configurations
  • Better for programmatic access
  • XPath query support
  • Easier to convert to actual JSON/YAML files
  • Natural structure for complex configs

XPath Queries (New in 3.0!)

Query YAML-formatted configurations using NSO-style XPath with optional context tracking:

from shconfparser.parser import Parser

# XPath requires YAML format
p = Parser(output_format='yaml')
data = p.read('running_config.txt')
tree = p.parse_tree(data)

# Simple queries
result = p.xpath('/hostname')
print(result.data)  # 'R1'

# Wildcards - find all interface duplex settings
result = p.xpath('/interface/*/duplex')
print(result.matches)  # ['auto', 'auto']
print(result.count)    # 2

# Predicates with slashes (network interface names)
result = p.xpath('/interface[FastEthernet0/0]/duplex')
print(result.data)  # 'auto'

# Recursive search - find anywhere in tree
result = p.xpath('//duplex')
print(result.matches)  # ['auto', 'auto']

# Predicate wildcards
result = p.xpath('/interface[FastEthernet*]/ip/address')
print(result.data)  # '1.1.1.1 255.255.255.0'

Context Options - Solve the "which interface?" problem:

# Problem: Can't identify source with wildcards
result = p.xpath('/interface/*/duplex')
print(result.matches)  # ['auto', 'auto'] - Which interface?

# Solution 1: context='none' (default - just values)
result = p.xpath('/

Related Skills

View on GitHub
GitHub Stars27
CategoryDevelopment
Updated2mo ago
Forks19

Languages

Python

Security Score

95/100

Audited on Jan 29, 2026

No findings