Skip to content

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

License

Notifications You must be signed in to change notification settings

network-tools/shconfparser

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

YAML Format Output

show run to YAML structure

Tree Structure

show run to tree 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

Single show command with YAML format (recommended):

from shconfparser.parser import Parser

# Use YAML format for cleaner output and XPath support
p = Parser(output_format='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'
Alternative: JSON format (backward compatible)
p = Parser()  # Default is JSON format (OrderedDict)
data = p.read('running_config.txt')
tree = p.parse_tree(data)
print(p.dump(tree, indent=4))

Multiple show commands in one file:

from shconfparser.parser import Parser

p = Parser(output_format='yaml')  # YAML 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))
Alternative: Access internal properties
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))

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 (New in 3.0!)

Parse configurations to JSON (OrderedDict) or YAML-friendly dict structures:

from shconfparser.parser import Parser

# Default: JSON format (OrderedDict - backward compatible)
p = Parser()
data = p.read('running_config.txt')
tree = p.parse_tree(data)  # Returns OrderedDict
print(type(tree))  # <class 'collections.OrderedDict'>

# YAML format: cleaner hierarchical structure
p = Parser(output_format='yaml')
data = p.read('running_config.txt')
tree_yaml = p.parse_tree(data)  # Returns dict with nested structure
print(type(tree_yaml))  # <class 'dict'>

# Override format per call
p = Parser()  # Default is JSON
tree_json = p.parse_tree(data)  # OrderedDict
tree_yaml = p.parse_tree(data, format='yaml')  # dict

# YAML structure example:
# Input: "interface FastEthernet0/0" with nested config
# JSON: {"interface FastEthernet0/0": {...}}
# YAML: {"interface": {"FastEthernet0/0": {...}}}

Format Comparison:

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

# YAML format - hierarchical and human-readable
{
    "interface": {
        "FastEthernet0/0": {
            "ip": {
                "address": "1.1.1.1 255.255.255.0"
            },
            "duplex": "auto"
        }
    }
}

Benefits of YAML format:

  • Cleaner hierarchy for nested configurations
  • Better for programmatic access
  • Easier to convert to actual YAML files
  • Natural structure for complex configs
  • Required for XPath queries

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('/interface/*/duplex', context='none')
print(result.matches)  # ['auto', 'auto']

# Solution 2: context='partial' (from wildcard match point)
result = p.xpath('/interface/*/duplex', context='partial')
print(result.matches)
# [{'FastEthernet0/0': {'duplex': 'auto'}},
#  {'FastEthernet0/1': {'duplex': 'auto'}}]

# Solution 3: context='full' (complete tree hierarchy)
result = p.xpath('/interface/*/duplex', context='full')
print(result.matches)
# [{'interface': {'FastEthernet0/0': {'duplex': 'auto'}}},
#  {'interface': {'FastEthernet0/1': {'duplex': 'auto'}}}]

# Path tracking (always available)
result = p.xpath('/interface/*/speed')
print(result.paths)
# [['interface', 'FastEthernet0/0', 'speed'],
#  ['interface', 'FastEthernet0/1', 'speed']]

XPath Features:

  • ✅ Absolute paths: /interface/FastEthernet0/0/duplex
  • ✅ Recursive search: //duplex (find anywhere)
  • ✅ Wildcards: /interface/*/duplex
  • ✅ Predicates: /interface[FastEthernet0/0]
  • ✅ Predicate wildcards: /interface[FastEthernet*]
  • ✅ Context tracking: See which match came from where
  • ✅ Path tracking: result.paths shows path components

XPathResult Structure:

result = p.xpath('//duplex')
print(result.success)   # True
print(result.data)      # First match: 'auto'
print(result.matches)   # All matches: ['auto', 'auto']
print(result.count)     # Number of matches: 2
print(result.query)     # Original query: '//duplex'
print(result.paths)     # Path to each match
print(result.error)     # Error message if failed

# Boolean check
if result:
    print(f"Found {result.count} matches")

Note: XPath queries only work with output_format='yaml'. JSON format (OrderedDict) preserves exact CLI structure and should use traditional dict navigation.

Alternative: Using Individual Components

For advanced users who need granular control
from shconfparser import Reader, ShowSplit, TreeParser, TableParser

# For multiple show commands
reader = Reader('multiple_commands.txt')
splitter = ShowSplit()
data = splitter.split(reader.data)  # Split only if multiple commands

# Use specific parsers
tree_parser = TreeParser()
table_parser = TableParser()

running = tree_parser.parse(data['running'])
cdp = table_parser.parse(data['cdp_neighbors'], header_names=headers)

💡 Remember: Use split() only when your file contains multiple show commands. For single command files, parse directly.

📖 For more examples, see docs/ folder.

Documentation

📚 Complete documentation: docs/README.md

For Users

Guide Description
Usage Examples Detailed parsing examples (tree, table, data)
API Reference Complete API documentation
Migration Guide Upgrade from v2.x to v3.0
Python Compatibility Python version support

For Contributors

Guide Description
Quick Start 5-minute contributor setup
Contributing Guide How to contribute
Architecture System design and structure
Business Standards Quality and compliance standards

Support

Getting Help

Frequently Asked Questions

Q: What Python versions are supported?
A: Python 3.8-3.13 are fully tested and supported.

Q: Does this work with my network vendor?
A: Yes! shconfparser is vendor-independent and works with any hierarchical configuration format.

Q: Are there any dependencies?
A: No runtime dependencies - uses only Python standard library.

Q: How do I migrate from v2.x?
A: The API is backward compatible. Just run pip install --upgrade shconfparser. See Migration Guide for details.

Community

  • 🌟 Star us on GitHub
  • 🤝 Contribute: See CONTRIBUTING.md
  • 📊 CI/CD: Automated testing on Python 3.8-3.13 across Ubuntu, macOS, Windows

License

MIT License © 2016-2025 Kiran Kumar Kotari

License: MIT

Special thanks to all contributors