Cisco-like な config のインデント構造に対応した grep コマンドの作成

Cisco IOS と IOS-XR の config のテキストファイルをいい感じに grep してくれる CLI コマンドを作成しました。その名も、**cfgrep**です。

:::note info
この記事はシスコの有志による [Cisco Systems Japan Advent Calendar 2023](https://qiita.com/advent-calendar/2023/cisco) の 2 日目として投稿しています。
:::


## 背景

多くのネットワークエンジニアは、ローカルに保存された config ファイルの内容を確認することがあると思います。そして、数千行または数万行の中から特定の内容を抽出するために、grepコマンド、lessコマンドの検索機能、またはテキストエディタの検索機能を利用していると思います。しかし、「grepで -A とか -B で近い行を抽出したいが不十分」、「検索結果が多すぎてコピーペーストが大変」といったことはないでしょうか。

そのような問題を解決するためのコマンドが **cfgrep** です。cfgrep はインデントの親子構造(階層構造)を理解して、 grepを行います。

## できること

cfgrep で指定した文字列パターン（正規表現）が現れる行を検索し、その親行と子行を再帰的に出力します。

具体的な使用例を以下に示します。例では、interface IDを正規表現で指定し、その親行と子行を再帰的に出力しています。他に有効に使用する例を、後述の「オプション」の項で紹介します。

```cisco_ios:IOS-XRのconfigをinterface IDの正規表現で検索した例
> cfgrep "TenGigE0/0/[0-2]/0" XR.txt
!
interface TenGigE0/0/0/0
 description test1
 ipv4 address 203.0.113.1 255.255.255.252
 ipv6 address 2001:db8:1::1/64
!
interface TenGigE0/0/2/0
 description test2
 ipv4 address 203.0.113.5 255.255.255.252
 ipv6 address 2001:db8:2::1/64
!
router ospf 1
 area 0
  interface TenGigE0/0/0/0
   cost 10
   network point-to-point
!
router ospf 1
 area 0
  interface TenGigE0/0/2/0
   cost 10
   network point-to-point
!
router ospfv3 1
 area 0
  interface TenGigE0/0/0/0
   cost 10
   network point-to-point
!
router ospfv3 1
 area 0
  interface TenGigE0/0/2/0
   cost 10
   network point-to-point
!
rsvp
 interface TenGigE0/0/0/0
  bandwidth 10000000
!
mpls traffic-eng
 interface TenGigE0/0/0/0
  backup-path tunnel-te 1
!
mpls ldp
 interface TenGigE0/0/0/0
!
multicast-routing
 address-family ipv4
  interface TenGigE0/0/2/0
   disable
!
router pim
 address-family ipv4
  interface TenGigE0/0/0/0
```

## 使用シチュエーション例

- 商用設計を確認するために、確認したい内容に関連する config を抽出して確認する。
- 商用 config から検証 config を作成するときに、必要な config だけを抽出する。
- 障害時に緊急で手順にない方法で復旧する際、焦って一部の config のみを抽出して作成するのではなく、漏れなく作成する。
    - たとえば interface の設定の移設だけでなく、関連するルーティング設定の移設も漏れなく行う

## インストール方法

cfgrep の利用には Python3 が必要です。事前にインストールしてください。

cfgrep は PyPI から pip でインストール可能です。

```shell
pip install cfgrep
```

もしインストール後も cfgrep コマンドが使用できない場合は、インストールされたディレクトリに PATH を通してください。私の Mac でpython3.8 を使用している環境では```/Users/$USER/Library/Python/3.8/bin```にPATHを通す必要がありました。
PyPI からはどうしてもうまくいかない場合は、[GitHub](https://github.com/tk-hayashi/cfgrep)の README に他のインストール方法を記載しましたので、試してみてください。

## 使用方法

下記の -h オプションの通りです。
入力はファイルにのみ対応しており、パイプ処理には対応していません。

```shell
 > cfgrep -h
Overview:
    cfgrep(c4grep, ccccgrep) is Cisco Config Context-aware Check grep

Usage:
    cfgrep <PATTERN> <FILE> [-i | --interface] [-b | --bgp] [-d | --description]
    cfgrep -h | --help

Options:
    -i, --interface    interface mode
    -b, --bgp          bgp mode
    -d, --description  to specify PATTERN by description
    -h, --help         display help
```

### オプション

help 以外では、３個のオプションがあります。

|  オプション  |  説明  |
| ---- | ---- |
|  -i  |  interface モード。interface の ipv4 と ipv6 アドレスに関連する config も出力します。|
|  -b  |  BGP モード。BGP neighbor に紐づく route-policy(IOS-XR の場合。IOS の場合は route-map)と bgp-group(IOS-XRの場合。IOS の場合は peer-group)も出力します。|
|  -d  |  description mode。description 行の親を PATTERN とみなして、その PATTERN で cfgrep を実行します。|

この説明ではそれぞれのオプションがどのように役立つのか分かりにくいので、使用例を下記に示します。

#### interface モード(-i)の例

- interface を指定すると、それに紐づく OSPF network 行も出力されています。

```cisco_ios:interface モード(-i)の例
> cfgrep -i "Vlan10$" ios.txt
!
interface Vlan10
 description test
 ip address 203.0.113.1 255.255.255.252
 ip ospf cost 10
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!
! ipv4 address search
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
router ospf 1
 network 203.0.113.0 0.0.0.3 area 0
 ```
 
#### BGP モード(-b)の例

- BGP neighbor address を指定すると、それに紐づく neighbor-group と route-policy も出力されています。

```cisco_ios:BGP モード(-b)の例
>  cfgrep -b 203.0.113.1 XR.txt

!
router bgp 65000
 neighbor 203.0.113.1
  use neighbor-group TEST
  description test
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!
! neighbor-group search
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
router bgp 65000
 neighbor-group TEST
  remote-as 65000
  update-source Loopback0
  address-family ipv4 unicast
   route-policy PASS in
   route-policy PASS out
   next-hop-self
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!
! route-policy search
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
route-policy PASS
  pass
end-policy
 ```

#### description モード(-d)の例

- description として CoreGroup を指定すると、それに紐づく interface TenGigE0/0/0/0, TenGigE0/0/2/0 と BGP の neighbor 198.51.100.2 を検索し、それに対してさらに cgrep しています。
- -d オプションを使用しないと、inteface ID と neighbor address くらいしか出力されません。

```cisco_ios:description モード(-d)の例
>  cfgrep -dbi CoreRouter XR.txt
!
interface TenGigE0/0/0/0
 description CoreRouter Te0/0/0/0
 ipv4 address 203.0.113.5 255.255.255.252
 ipv6 address 2001:db8:beef:1::1/64
 ipv6 enable
!
interface TenGigE0/1/0/0
 description CoreRouter Te0/1/0/0
 ipv4 address 203.0.113.9 255.255.255.252
 ipv6 address 2001:db8:beef:2::1/64
 ipv6 enable
!
router bgp 65000
 neighbor 198.51.100.2
  use neighbor-group CoreGroup
  description CoreRouter
  address-family ipv4 unicast
   route-policy PASS out
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!
! route-policy search
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
route-policy PASS
  pass
end-policy
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!
! neighbor-group search
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
router bgp 65000
 neighbor-group CoreGroup
  remote-as 65000
  update-source Loopback0
  address-family ipv4 unicast
   route-policy DROP in
   route-policy PASS out
   next-hop-self
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!
! route-policy search
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
route-policy PASS
  pass
end-policy
!
route-policy DROP
  drop
end-policy
```
</details>


### 対応機器

- IOS-XR
- IOS
- その他インデントで親子構造となる config の機器 (オプションは対応していません)

xmlやjsonのような構造化されたファイル形式、または IOS-XR の show run formal や JUNOS の display  set のような flatten されたファイル形式には対応していません。cfgrepは、これらの機械が読みやすい(i.e. parse しやすい)形式ではなく、traditional(?)な形式のみを対象としています。

## (余談ポエム)

- [DevNet の Code Exchange](https://developer.cisco.com/codeexchange/github/repo/tk-hayashi/cfgrep/)にも登録しています。 
- オプションは私が当時すぐに欲しかった機能のみを実装しているので、物足りないかもしれません。
- 個人的にはとても便利なコマンドなのですが、公開から4年経っても、私以外に使用者はほとんどいないようで、この機会にぜひご利用いただければと思います。