Text Processing for Network Engineers Part 3: sed and Editing Configs Without Opening a Single File
grep finds. sed changes. This part is stream editing: bulk text changes across saved configs without opening a single file in an editor.
The one command that matters: s///
Everything else in sed is context for making this one command land on the right lines:
sed 's/old/new/'
By default that replaces only the first match on each line. Add the g flag to replace every match on every line:
sed 's/old/new/g'
Case-insensitive matching is I (capital, GNU sed):
sed 's/vlan/VLAN/gI'
And because sed’s default dialect is BRE (see Part 1), the same escaping rules apply: sed -E switches to ERE so +, ?, and | work unescaped.
In-place editing, with a safety net
By default sed prints the result to stdout and leaves the original file untouched, which is the safe way to test a substitution before committing to it:
sed 's/10.50.0.15/10.50.0.20/' ntp-config.txt # prints to screen, file unchanged
-i edits the file directly. On GNU sed, -i takes an optional backup suffix argument with no space:
sed -i.bak 's/10.50.0.15/10.50.0.20/' ntp-config.txt
That writes the changed file in place and leaves ntp-config.txt.bak containing the original. Always do this the first few times you run an in-place sed against anything that matters. GNU and BSD sed disagree on -i syntax (BSD/macOS sed requires the suffix as a separate argument, even if empty: sed -i '' 's/.../.../ '), which is a portability trap if you’re scripting for both a Linux jump box and a macOS laptop.
Address ranges: which lines does this apply to
Unqualified, s/// runs on every line. Prefix it with an address to restrict it:
sed '5s/old/new/' # only line 5
sed '10,20s/old/new/' # lines 10 through 20
sed '/interface Gi0\/1/,/^!/s/old/new/' # from a matching line to the next "!" (end of an IOS block)
That last form, a pattern range rather than a line-number range, is the one that matters for config files, because config line numbers are meaningless (they shift every time the config changes) but structural markers, interface, the next !, the next blank line, are stable. “From this interface stanza to the end of the block” is exactly how you scope a change to one interface instead of every occurrence of a string across the whole file.
Deleting, inserting, and appending
d deletes matched lines outright:
sed '/^!/d' running-config.txt # strip every IOS comment/separator line
sed '/^\s*$/d' running-config.txt # strip blank lines
i\ inserts a line before a match, a\ appends a line after it:
sed '/interface Gi0\/1/a\
description Added by config audit 2026-07' running-config.txt
That’s a real use case: bulk-tagging every interface stanza across a fleet with an audit marker, without manually opening each file.
Multiple edits in one pass: -e and semicolons
Chain edits with -e or semicolons rather than piping sed into sed:
sed -e 's/old-ntp/new-ntp/' -e 's/old-radius/new-radius/' config.txt
sed 's/old-ntp/new-ntp/; s/old-radius/new-radius/' config.txt
Both apply both substitutions in a single pass over the file, which matters more than it sounds: a single pass is one read of the file and one set of line-by-line decisions, not two separate full scans.
The worked example: renumbering a subnet across 200 saved configs
This is the job that makes sed worth learning properly rather than reaching for a text editor’s find-and-replace. A /24 is being renumbered from 10.50.0.0/24 to 10.60.0.0/24 across every device that references it, and the config backups live one file per device:
for f in configs/*.cfg; do
sed -i.bak 's/10\.50\.0\./10.60.0./g' "$f"
done
The dots in 10\.50\.0\. are escaped because in regex a bare . matches any character, and an un-escaped pattern here would also match 10x50x0x if that string existed anywhere (see Part 1’s note on -F, the same logic applies inside sed’s pattern). The loop runs the same substitution against every file in the directory, each with its own .bak safety copy, and does in one command what would otherwise be 200 manual edits.
Verify before you trust it:
grep -rl '10.50.0.' configs/ # should now be empty
grep -rl '10.60.0.' configs/ # should now list every affected file
That two-line check after the bulk edit is not optional. sed will happily “succeed” on a pattern that matched nothing, silently. There’s no error for “found zero lines to change,” so confirming the result with grep is the only way to know the edit actually did what you intended, rather than quietly doing nothing because of a typo in the pattern.
What sed doesn’t do well
sed operates line by line (technically, one “pattern space” at a time, with a hold buffer for the rarer multi-line tricks, which this series won’t dwell on because they’re a niche inside a niche). It has no concept of columns, fields, or arithmetic. Renumbering an IP is a like-for-like string swap, exactly sed’s job. Recalculating a checksum, summing a column of byte counters, or reformatting show interface output into a table is a different job, and that job belongs to Part 4’s tool: awk.