About 70 percent of the work in network automation is shoveling text — IPs, MACs, hostnames, banner output, log lines — from one shape into another. Today covers the small handful of built‑in string operations that handle most of that shoveling. No regex (that’s Day 9), no external libraries — just str methods that appear in nearly every script from here on.
This is Day 3 of the 21‑post Python for Network Engineers series. A REPL opened with python makes it easy to try every snippet below while reading.
Strings are sequences — they can be indexed and sliced
A string behaves like a list of characters with zero‑based indexing:
>>> intf = "GigabitEthernet0/1"
>>> intf[0]
'G'
>>> intf[-1] # negative indexes count from the right
'1'
>>> intf[-3:] # last three characters
'0/1'
>>> intf[:7] # first seven characters
'Gigabit'
The general slice form is s[start:stop:step]. start is inclusive, stop is exclusive (just like range), and any of them can be omitted or negative. s[::-1] reverses a string — handy for the rare case that needs a name compared backwards.
One trap: indexing past the end of a string raises IndexError. Slicing past the end just returns what’s available, no error. So when the exact length is unknown, slicing is the safer choice.
The string methods worth knowing cold
| Method | What it does | Network use |
|---|---|---|
.split(sep) |
Splits into a list on sep |
Break “10.0.0.1” into octets |
.strip() |
Removes leading/trailing whitespace | Clean lines from readlines() |
.upper() / .lower() |
Change case | Normalize hostnames before comparison |
.replace(a, b) |
Returns string with all a → b |
Convert MAC formats |
.startswith(p) / .endswith(s) |
Boolean prefix/suffix test | Filter “show ip route” lines |
.join(iterable) |
Glue list elements with the string | Rebuild dotted notation |
in operator |
Substring membership | "GigabitEthernet" in line |
Strings in Python are immutable. Every method above returns a new string; the original is never modified. So intf.upper() alone does nothing useful — the result has to be assigned: intf = intf.upper().
Splitting an IP into octets
>>> ip = "192.168.10.55"
>>> ip.split(".")
['192', '168', '10', '55']
>>> ip.split(".")[1]
'168'
>>> ".".join(ip.split(".")[:3]) + ".0"
'192.168.10.0' # quick subnet base for a /24, ugly but works
Note that split returns strings, not integers — '168', not 168. Math requires wrapping with int(). From Day 8 the ipaddress module handles this properly with subnet awareness, but the manual version is worth knowing for one‑off awk‑style work.
Normalizing MAC addresses
Cisco shows MACs as aabb.ccdd.eeff. Linux shows them as aa:bb:cc:dd:ee:ff. Some vendors use aa-bb-cc-dd-ee-ff. Converting between these comes up constantly. With pure string ops:
def normalize_mac(mac):
"""Return MAC as colon-separated lowercase: aa:bb:cc:dd:ee:ff"""
clean = mac.lower().replace(":", "").replace("-", "").replace(".", "")
if len(clean) != 12:
raise ValueError(f"Not a 12-hex-digit MAC: {mac!r}")
return ":".join(clean[i:i+2] for i in range(0, 12, 2))
print(normalize_mac("AABB.CCDD.EEFF")) # aa:bb:cc:dd:ee:ff
print(normalize_mac("aa-bb-cc-dd-ee-ff")) # aa:bb:cc:dd:ee:ff
print(normalize_mac("AA:BB:CC:DD:EE:FF")) # aa:bb:cc:dd:ee:ff
Three calls to .replace, one length check, one slice loop wrapped in ":".join(). That’s a complete utility good for years of reuse.
Cleaning up banner / show output
Output from a router often comes back with trailing whitespace, leading spaces, or stray \r\n from the SSH transport. .strip() handles it:
raw = " GigabitEthernet0/1 is up, line protocol is up \r\n"
print(raw.strip())
# 'GigabitEthernet0/1 is up, line protocol is up'
To filter only interface‑status lines from a multi‑line block, combine .split("\n") with a comprehension and .startswith:
show_int = """GigabitEthernet0/1 is up, line protocol is up
Hardware is iGbE, address is aabb.cc00.0001
GigabitEthernet0/2 is administratively down, line protocol is down
Hardware is iGbE, address is aabb.cc00.0002"""
status_lines = [line for line in show_int.split("\n") if line.startswith("Gigabit")]
for line in status_lines:
print(line)
That’s a full one‑liner for “give me only the headlines from show interfaces.” The comprehension syntax gets formalized tomorrow.
f‑strings: more than just {var}
f‑strings appeared yesterday. They have a small format mini‑language after a colon that’s worth memorizing — it removes the need for str.format() calls and ad‑hoc padding loops:
name = "Gi0/1"
in_pkts = 1234567
util = 0.7634
print(f"{name:<25} {in_pkts:>12,} {util:.2%}")
# Gi0/1 1,234,567 76.34%
:<25— left‑align in a 25‑char field (great for column reports):>12,— right‑align in a 12‑char field, with thousands separators:.2%— render as a percentage with two decimals:08x— render an int as 8‑digit lowercase hex, zero‑padded — useful for MAC vendor OUIs
Ten minutes in the REPL trying these pays off. They turn ad‑hoc print calls into reports that fit on a screen.
One bigger example: extract management IPs from a CDP table
cdp = """Device ID Local Intf Hold Capability Platform Port ID
core-sw01 Gig0/0 142 R S I WS-C2960 Gig1/0/24
edge-rtr02 Gig0/1 170 R ISR4321 Gig0/0/0
ap-floor3 Gig0/2 162 T AIR-CAP3702 Gig0
"""
for line in cdp.split("\n"):
line = line.strip()
if not line or line.startswith("Device"):
continue # skip header and blanks
parts = line.split()
device_id, local_intf = parts[0], parts[1]
print(f"{device_id:<15} reachable on {local_intf}")
Six lines of pure built‑in string code, no imports. Every operation covered today is in there: .split, .strip, startswith, slicing via positional unpacking, and an f‑string with alignment.
Exercises
- Given
intf = "TenGigabitEthernet1/0/24", write one expression that returns just the slot number"1"as a string. - Given
" csr1000v# ", return the bare hostname"csr1000v"with no whitespace and no trailing#. - Convert
"aabb.ccdd.eeff"to"AA-BB-CC-DD-EE-FF"(uppercase, hyphen‑separated). Adapt thenormalize_macidea but change the formatting. - Given a list of interface names
["Gi0/1", "Gi0/2", "Te1/0/1", "Lo0"], print only the ones that start with"Gi", one per line. - Stretch: given the multi‑line
show ip int brief‑style block below, print only the interfaces whose protocol isup, formatted as<name> -> <ip>in left‑aligned 25‑char columns.Interface IP-Address OK? Method Status Protocol GigabitEthernet0/0 10.0.0.1 YES NVRAM up up GigabitEthernet0/1 unassigned YES unset administratively down down GigabitEthernet0/2 10.0.1.1 YES NVRAM up up Loopback0 192.168.0.1 YES NVRAM up up
Answers
Show answer 1
>>> intf = "TenGigabitEthernet1/0/24"
>>> intf.split("Ethernet")[1].split("/")[0]
'1'
Split on the literal word "Ethernet" to get the trailing "1/0/24", then split on "/" and take the first piece. There are many ways to do this — intf[len("TenGigabitEthernet"):].split("/")[0] works too.
Show answer 2
>>> " csr1000v# ".strip().rstrip("#")
'csr1000v'
.strip() removes all leading/trailing whitespace; .rstrip("#") then removes any trailing # characters. Order matters — strip whitespace first so the # lands at the actual end.
Show answer 3
def to_dash_upper(mac):
clean = mac.lower().replace(":", "").replace("-", "").replace(".", "")
return "-".join(clean[i:i+2] for i in range(0, 12, 2)).upper()
print(to_dash_upper("aabb.ccdd.eeff")) # AA-BB-CC-DD-EE-FF
Same pattern as normalize_mac — strip all separators, slice into pairs, join with the new separator, then uppercase the whole result.
Show answer 4
intfs = ["Gi0/1", "Gi0/2", "Te1/0/1", "Lo0"]
for name in intfs:
if name.startswith("Gi"):
print(name)
Or as a one‑liner with a comprehension: print("\n".join(n for n in intfs if n.startswith("Gi"))) — but the explicit loop is fine and reads better on day one.
Show answer 5
block = """Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 10.0.0.1 YES NVRAM up up
GigabitEthernet0/1 unassigned YES unset administratively down down
GigabitEthernet0/2 10.0.1.1 YES NVRAM up up
Loopback0 192.168.0.1 YES NVRAM up up
"""
for line in block.split("\n"):
if not line or line.startswith("Interface"):
continue
parts = line.split()
name, ip, protocol = parts[0], parts[1], parts[-1]
if protocol == "up":
print(f"{name:<25} -> {ip:<25}")
Skip blanks and the header, split each line on whitespace, take name (first), ip (second), and protocol (last). Filter on protocol == "up" and format with f‑string alignment. This is the shape of nearly every show‑output parser written before TextFSM arrives in Week 3.
Coming tomorrow
Day 4: Lists, Tuples, Dicts, Sets — Modeling Devices and Inventories. Next up are the data structures every real automation script revolves around, including the dictionary form Netmiko expects for a device connection. By the end, an inventory of mock devices exists in pure Python, ready to loop over.