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

1 · Shell fluency

The shell is the network engineer's force multiplier. Everything here builds toward one thing: doing something to every device at once, and being told only about the ones that are wrong.


Pipes: the whole idea

A pipe sends one command's output into the next as input. Small tools, chained.

docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary" | grep Estab | wc -l

Three trivial tools — run a command, filter lines, count them — answering "how many BGP peers are up." No tool knows about the others, and none of them knows about BGP.

That's the Unix bargain: nothing does much alone, and combinations do almost anything.


Redirection and the two output streams

Every command writes to stdout (normal output) and stderr (errors). They're separate on purpose, so you can keep one and discard the other.

Syntax Does
> file stdout to file, overwriting
>> file stdout to file, appending
2> file stderr to file
2>&1 stderr into stdout — merge them
> file 2>&1 both to file
2>/dev/null discard errors
\| tee file to file and the screen
# capture configs, log errors separately
for n in r1 r2 r3; do
  docker exec clab-bgp-lab-$n Cli -p 15 -c "show running-config" \
    > configs/$n.cfg 2>> errors.log
done

> destroys the file before the command runs

sort file.txt > file.txt gives you an empty file. The shell truncates the target first, then runs the command against nothing. Write to a new file and move it.


Exit codes: how a script knows it worked

Every command returns a status. 0 means success, anything else is a failure — backwards from what most people expect, but it lets different failures have different numbers.

docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary" >/dev/null
echo $?      # 0

This is what makes conditionals work. grep returns 0 when it finds something, 1 when it doesn't:

if docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary" | grep -q Estab; then
  echo "BGP is up"
else
  echo "BGP is DOWN"
fi

-q means quiet — print nothing, just set the exit code. That's the idiom for "test whether this appears."

Chaining on success or failure:

command_a && command_b     # b only if a succeeded
command_a || command_b     # b only if a failed
command_a ; command_b      # b regardless
mkdir -p backups && cp *.cfg backups/     # don't copy if mkdir failed
ping -c1 10.0.12.1 >/dev/null || echo "10.0.12.1 unreachable"

Variables and quoting

DEVICE="clab-bgp-lab-r1"
docker exec "$DEVICE" Cli -p 15 -c "show version"

Quote your variables. Unquoted, the shell splits on spaces and expands *:

FILE="my config.txt"
rm $FILE      # ✗ tries to delete "my" AND "config.txt"
rm "$FILE"    # ✓
Quoting Behaviour
"$VAR" expands the variable — the default choice
'$VAR' literal $VAR, no expansion
$(command) run it, substitute the output
PEERS=$(docker exec clab-bgp-lab-r1 Cli -p 15 -c "show ip bgp summary" | grep -c Estab)
echo "r1 has $PEERS peers"

Heredocs, and the trap you already met

A heredoc feeds a block of text to a command:

docker exec -i clab-bgp-lab-r1 Cli -p 15 <<'EOF'
configure
router bgp 65001
EOF

Quoting the delimiter matters. <<'EOF' passes the text literally; <<EOF expands $variables inside it. Use quoted unless you specifically want substitution — device configs are full of $ characters.

And a heredoc into docker exec needs -i, or stdin is never attached and nothing runs. See lab troubleshooting.


Loops: where it stops being typing

for n in r1 r2 r3; do
  printf "%-4s " "$n"
  docker exec clab-bgp-lab-$n Cli -p 15 -c "show ip bgp summary" | grep -c Estab
done

Run against the live Phase 1 fabric:

r1   2
r2   1
r3   1

Loop over a file of device names instead and the same code handles a thousand:

while read -r host; do
  echo "=== $host ==="
  ssh "$host" "show ip bgp summary"
done < devices.txt

read -r stops backslashes being interpreted — always use it.


Report only what's broken

Listing everything doesn't scale: nobody reads three hundred lines of "ok." A useful check is silent when healthy.

for n in r1 r2 r3; do
  if docker exec clab-bgp-lab-$n Cli -p 15 -c "show ip bgp summary" 2>/dev/null \
     | grep -qE "Idle|Active"; then
    echo "$n: DOWN PEER"
  else
    echo "$n: ok"
  fi
done
r1: ok
r2: ok
r3: ok

Drop the else branch and it prints nothing at all when the fabric is healthy — which is what you want in cron or CI. Silence means fine; output means look.

Match every failure state, not just the good one

grep -q Estab tests that something is established. It stays quiet when a second peer is down. Test for the bad states — Idle|Active — or count and compare against what you expect.

A check that only recognises success is silent during an outage, and silence looks identical to healthy.


Writing it as a script

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

# check-bgp.sh — report devices with any peer not established
DEVICES=("r1" "r2" "r3")
FAILED=0

for n in "${DEVICES[@]}"; do
  out=$(docker exec "clab-bgp-lab-$n" Cli -p 15 -c "show ip bgp summary" 2>/dev/null) || {
    echo "$n: UNREACHABLE"; FAILED=1; continue
  }
  if grep -qE "Idle|Active" <<<"$out"; then
    echo "$n: DOWN PEER"; FAILED=1
  fi
done

exit $FAILED
chmod +x check-bgp.sh
./check-bgp.sh

set -euo pipefail belongs at the top of every script you write:

Flag Effect
-e exit on any command failure
-u error on undefined variables — catches typos
-o pipefail a pipeline fails if any stage fails, not just the last

Without pipefail, false \| true succeeds. That's how a broken backup script reports success for months.

The script exits non-zero when anything is wrong, which is what lets CI, cron or Ansible act on it.


Worth knowing

Command Use
history \| grep ssh find that command you ran last week
!! / sudo !! repeat last command, optionally with sudo
Ctrl-R search history interactively
cmd1 \| tee out.txt \| cmd2 tap a pipeline mid-flow
watch -n5 'cmd' re-run every 5s
timeout 10 cmd give up after 10s — essential for unreachable devices
xargs -P8 run 8 in parallel
# poll a fabric in parallel, 8 at a time, none hanging longer than 10s
cat devices.txt | xargs -P8 -I{} timeout 10 ssh {} "show ip bgp summary"

Next: Parsing device output → — turning show output into data.