မာတိကာ သို့ သွားရန်

2 · Parsing device output

Network devices emit text designed for humans. Automation needs data. This page is about the gap between the two.


grep — find the lines

grep Estab summary.txt              # lines containing Estab
grep -v Estab summary.txt           # lines NOT containing it
grep -i estab summary.txt           # case-insensitive
grep -c Estab summary.txt           # count matches
grep -q Estab summary.txt           # silent; exit code only
grep -E "Idle|Active" summary.txt   # regex alternation
grep -A2 -B1 "Ethernet1" cfg.txt    # 2 lines after, 1 before

The two that matter most in scripts:

  • -q — no output, just an exit code. The idiom for "does this appear?"
  • -E — extended regex, so you can write Idle|Active and catch every failure state in one pass.
# every interface stanza in a config
grep -A5 "^interface" running-config.txt

awk — pick out fields

awk splits each line into fields on whitespace and lets you print or test them. $1 is the first field, $2 the second, $0 the whole line.

Run against the live Phase 1 fabric:

docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary" \
  | awk '/Estab/ {print $1, $3, $9}'
2.2.2.2 65001 Estab
10.0.13.3 65002 Estab

That's the whole pattern: /match/ { action }. Only lines matching the pattern get the action.

More that earns its keep:

awk '{print $1}'                        # first column
awk -F: '{print $1}'                    # split on ':' instead
awk '$3 > 100 {print $1}'               # numeric comparison
awk '/Estab/ {n++} END {print n+0}'     # count matches
awk 'NF'                                # drop blank lines
awk '{print NR": "$0}'                  # number the lines

A practical one — flag peers that aren't established:

docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary" \
  | awk '$1 ~ /^[0-9]+\./ && $9 != "Estab" {print "DOWN: " $1}'

$1 ~ /^[0-9]+\./ restricts it to lines starting with an IP, skipping headers.

Why awk beats cut

cut -d' ' -f3 treats every space as a separator, so runs of spaces produce empty fields — and device output is full of aligned columns. awk collapses whitespace automatically, which is almost always what you want.


sed — edit the stream

Mostly for substitution:

sed 's/old/new/'          # first occurrence per line
sed 's/old/new/g'         # all occurrences
sed 's|10.0.12|10.0.99|g' # alternative delimiter — no escaping slashes
sed -n '5,10p'            # print lines 5-10 only
sed '/^!/d'               # delete comment lines
sed -i.bak 's/a/b/g' f    # edit in place, keeping f.bak

Cleaning a config for diffing — strip comments, blanks and timestamps:

sed -e '/^!/d' -e '/^$/d' -e '/Last configuration change/d' running-config.txt

That matters: without it, every diff shows a changed timestamp and you learn to ignore diffs. See git.

sed -i without a suffix

On GNU/Linux sed -i edits in place. On macOS/BSD it requires an argument, so sed -i 's/a/b/' consumes your next argument as the suffix and fails confusingly. sed -i.bak works on both — and keeps a backup.


jq — for JSON

Modern NOSes speak JSON, which is far better than scraping text. EOS takes | json on most show commands, and REST/gNMI APIs return it natively.

docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary | json" \
  | jq '.vrfs.default.peers | keys'
jq '.'                          # pretty-print
jq -r '.name'                   # raw output, no quotes
jq '.items[]'                   # iterate an array
jq '.items[] | select(.up)'     # filter
jq -r '.peers | to_entries[] | "\(.key) \(.value.peerState)"'

-r is important in scripts — without it strings keep their quotes and comparisons fail.

Prefer JSON to text whenever the device offers it

Text output is formatted for humans and changes between software versions — column widths shift, headers get reworded, and your awk $9 silently becomes the wrong field.

JSON keys are part of the API and far more stable. If a device supports | json, use it and parse with jq. Reserve awk for devices that don't.


sort, uniq, and counting

sort file                  # alphabetical
sort -u file               # sorted, deduplicated
sort -n file               # numeric
sort -k3 file              # by third column
uniq -c                    # count runs — input must be sorted

The classic combination — find the most common thing in a log:

grep ERROR app.log | awk '{print $5}' | sort | uniq -c | sort -rn | head

Read right to left: extract field 5, sort so identical values are adjacent, count each run, sort numerically descending, show the top few. That's a frequency table in one line, and it works on any log you'll ever meet.


Comparing two devices

diff <(ssh r1 "show running-config") <(ssh r2 "show running-config")

<(...) is process substitution — it makes a command's output look like a file. No temporary files, and it works with any tool expecting filenames.

# ignore whitespace and comment lines
diff -w <(sed '/^!/d' r1.cfg) <(sed '/^!/d' r2.cfg)

Putting it together

A config drift check — grab the config, normalise it, compare against the last-known-good:

#!/usr/bin/env bash
set -euo pipefail

for n in r1 r2 r3; do
  docker exec "clab-bgp-lab-$n" Cli -p 15 -c "show running-config" \
    | sed -e '/^!/d' -e '/^$/d' \
    > "current/$n.cfg"

  if ! diff -q "baseline/$n.cfg" "current/$n.cfg" >/dev/null 2>&1; then
    echo "=== $n DRIFTED ==="
    diff "baseline/$n.cfg" "current/$n.cfg" || true
  fi
done

Silent when nothing changed. || true stops set -e killing the script, since diff exits 1 when files differ — a normal outcome here, not an error.


Next: SSH properly → — the transport all of this runs over.