Text Processing for Network Engineers Part 8: diff, patch, and Comparing Configs Before You Push
Every tool so far in this series changes or searches text. diff and patch are the pair that answer a different question: what, exactly, changed between two versions of the same file, and can that exact change be replayed somewhere else.
Unified diff: the format everything else builds on
diff -u old-config.txt new-config.txt
The -u (unified) format is the one worth learning, because it’s the format git, GitHub, and every code-review tool already uses:
--- old-config.txt
+++ new-config.txt
@@ -12,7 +12,7 @@
interface GigabitEthernet0/1
description Uplink to core
- ip address 10.50.0.1 255.255.255.0
+ ip address 10.60.0.1 255.255.255.0
no shutdown
--- and +++ name the two files being compared. @@ -12,7 +12,7 @@ is a hunk header: starting at line 12 of the old file, 7 lines of context are shown; starting at line 12 of the new file, 7 lines of context are shown. Lines prefixed - were removed, + were added, unprefixed lines are unchanged context shown for orientation. Reading that format fluently is worth doing deliberately once, because it’s the same format git diff prints, the same format code review tools render, and the same format a patch file is made of.
diff -r: comparing whole directory trees
For a before/after comparison of an entire fleet’s worth of config backups, not just one file, -r makes diff recursive:
diff -rq configs-before/ configs-after/
-q (brief) prints only which files differ, not the full diff content, which is the right first pass on a large tree: get the list of what changed, then diff the individual files that matter:
diff -u configs-before/core-sw01.cfg configs-after/core-sw01.cfg
Readability: colordiff and wdiff
Plain diff -u output is readable but visually flat in a terminal with no syntax highlighting. colordiff wraps diff’s output and colors the +/- lines, which makes a long diff considerably faster to scan without changing anything about the underlying comparison. wdiff does word-level diffing instead of line-level, useful when a single long line (a long ACL or route-map statement) has one word changed in the middle, since a line-level diff just shows the whole line as removed-then-added, telling you nothing about what on that line moved.
Generating and applying a patch
A unified diff is a patch file, nothing more needs generating:
diff -u old-config.txt new-config.txt > change.patch
Apply that same change to a different file with matching (or close enough) original content:
patch old-config.txt < change.patch
The fleet-wide version of that, applying one confirmed-good change to every device backup that still has the old value:
for f in configs/*.cfg; do
patch --dry-run "$f" < change.patch && patch "$f" < change.patch
done
--dry-run first is the important habit: it tells you whether the patch would apply cleanly against that specific file (some devices in the fleet may already have a locally-modified version that doesn’t match the expected context, and patch will refuse rather than guess), and only actually writes the change if the dry run succeeded. This is meaningfully safer than the sed-based bulk edit from Part 3 for one specific reason: patch verifies the surrounding context lines match before it touches anything, so a file that’s drifted from what you expected fails loudly instead of silently mismatching.
When generic diff isn’t enough: the case for a config-aware tool
Generic line-by-line diff has a real limitation once the file format has its own semantics. FortiGate configs, in particular, are a case where two functionally identical configs can differ in line order (block ordering in a config firewall policy section, or the order objects were created in, neither of which changes what the firewall actually does) while a line-by-line diff reports the whole file as different, because it’s comparing text position, not configuration meaning.
That gap is exactly why fgt-config-diff exists as its own project rather than a five-line diff wrapper: it parses FortiGate’s config/edit/set/next/end block structure into a tree first, then compares the trees for added, removed, and modified entries, which correctly treats reordered-but-identical blocks as unchanged. Generic diff is right for the vast majority of “what changed in this text file” questions this series covers. It stops being right the moment the file format has structure that a line-based comparison can’t see, and knowing where that line sits is as valuable as knowing the tool itself.
The habit this all supports
Before any bulk sed edit from Part 3, or any fleet-wide macro replay from Part 6, run the change against one file first, diff the before and after, and read it. That five-second check is the difference between confirming the edit did exactly what you intended and discovering an off-by-one problem after it’s already live on 200 devices. Part 9 covers the tools that make running any of these edits, safely, across an entire directory tree of files, a repeatable operation rather than a hand-typed loop each time: find and xargs.