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 we cover 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 you’ll use in every script you write from here on.
This is Day 3 of the 21‑post Python for Network Engineers series. You should be able to open a REPL with python and try every snippet below as you read.
Strings are sequences — you can index and slice them
Treat a string 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 time you need to compare a name 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 you don’t know the exact length, prefer slicing.
The string methods you’ll use weekly
| 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 — you have to assign the result: 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
Notice that split returns strings, not integers — '168', not 168. To do math, wrap with int(). From Day 8 the ipaddress module will handle this properly with subnet awareness, but the manual version is good to know 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. You’ll convert between these 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 you’ll reuse for years.
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() is your friend:
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.” We’ll formalize the comprehension syntax tomorrow.
f‑strings: more than just {var}
You met f‑strings 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
Spend ten minutes in the REPL trying these. 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. Read it once and notice every operation we covered today is in there: .split, .strip, startswith, slicing via positional unpacking, 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). Use thenormalize_macidea but adapt the formatting. - You have 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. Many ways to skin 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 # is 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 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 you’ll write before we get to TextFSM in Week 3.
Coming tomorrow
Day 4: Lists, Tuples, Dicts, Sets — Modeling Devices and Inventories. We’ll build the data structures every real automation script revolves around, including the dictionary form Netmiko expects when you connect to a device. By the end you’ll have an inventory of mock devices in pure Python, ready to loop over.