【Ansible】textfsmのparserは簡単に作れる～応用編～

### はじめに
<バージョン>
ansible 2.9.7
textfsm 1.1.0
　前回の記事[【Ansible】textfsmのparserは簡単に作れる～基礎編～](https://qiita.com/masa2223/items/611e59f1599ade9e2d1b)
にて、__parseする各データが1行に入っており、検索パターンが少ない場合__を紹介しましたので、
今回はparseする各データが複数行ある場合の以下2パターンを紹介します。
なお、parseするためのplaybookの構成は前回の記事と同じなので説明は割愛します。
1. 行頭と行末が決まっている場合
2. 行頭が決まっているが、行末が決まっていない場合

ここからの説明において以下の用語の定義を行います。（あくまでもこの記事内のみです）

### 1. 行頭と行末が決まっている場合
#### 1-1. サンプルデータ

　今回は、__id <番号>__配下のデータを抽出します。tagはダミーです。
__parseするデータにおいて行頭と行末が決まっている場合はダミー回避可能__なので入れておきます。
parseするデータごとに行頭は「id <番号>」、行末は「!」で固定です。
Cisco系の機器でshow running-configは項目の最後が「!」で終わっていることが多いので
parserが作りやすいかもしれません。

```text
tag 1
  name Kei
!
id 1
  name Taro
  age 20
  comment hello
!
id 2
  name Hanako
  age 22
!
id 3
  name Satoru
  age 1
!
```

#### 1-2. fsmファイル～成功例～
　parseする各データが複数行ある場合は、Startの部分にparseするデータの1行目
だけを書きます。この条件にマッチしたらpeopleという行にスキップするようにしています。
最終行は「!」で決まっているので、__Record Start__と書くことでで結果を出力しつつ、
__Startの行に戻ってparseするデータの1行目の検索を再開__します。

```text:fsm_sample_1.textfsm
Value id (\d+)
Value name ([A-Z][a-z]+)
Value age (\d+)
Value comment (\w+)

Start
  ^id ${id} -> people

people
  ^\s+name ${name}
  ^\s+age ${age}
  ^\s+comment ${comment}
  ^!$$ -> Record Start
```

```yaml:出力
  - age: '20'
    comment: hello
    id: '1'
    name: Taro
  - age: '22'
    comment: ''
    id: '2'
    name: Hanako
  - age: '1'
    comment: ''
    id: '3'
    name: Satoru
```

#### 1-3. fsmファイル～失敗例1～
　最終行を、Record StartではなくRecordにした場合を考えてみます。
このようにすると、people内でループが回ってしまうのでidが取得されません。

```text:fsm_sample_2.textfsm
Value id (\d+)
Value name ([A-Z][a-z]+)
Value age (\d+)
Value comment (\w+)

Start
  ^id ${id} -> people

people
  ^\s+name ${name}
  ^\s+age ${age}
  ^\s+comment ${comment}
  ^!$$ -> Record
```

```yaml:出力
  - age: '20'
    comment: hello
    id: '1'
    name: Taro
  - age: '22'
    comment: ''
    id: ''  # idがない
    name: Hanako
  - age: '1'
    comment: ''
    id: '' # idがない
    name: Satoru
```

#### 1-4. fsmファイル～失敗例2～
　そもそもpeopleという変数を使わない場合を考えてみます。
このようにすると、最終行の「!」が来るたびに出力するのでtag配下（Kei）も
取得されてしまいます。

```text:fsm_sample_3.textfsm
Value id (\d+)
Value name ([A-Z][a-z]+)
Value age (\d+)
Value comment (\w+)

Start
  ^id ${id}
  ^\s+name ${name}
  ^\s+age ${age}
  ^\s+comment ${comment}
  ^!$$ -> Record
```

```yaml:出力
  - age: ''
    comment: ''
    id: ''
    name: Kei
  - age: '20'
    comment: hello
    id: '1'
    name: Taro
  - age: '22'
    comment: ''
    id: '2'
    name: Hanako
  - age: '1'
    comment: ''
    id: '3'
    name: Satoru
```

### 2. 行頭が決まっているが、行末が決まっていない場合
#### 2-1. サンプルデータ
　__行頭が決まっているが、行末が決まっていない場合はダミー回避が厳しそう__なので
ダミーは入れていません。（ご存じの方いらっしゃいましたら教えていただけると幸いです）
[networktocode/ntc-templates - GitHub](https://github.com/networktocode/ntc-templates)にある、「cisco_ios_show_interfaces_switchport.textfsm」も
後で紹介するfsmファイルのような記入方法をしていました。

```text
id 1
  name Taro
  age 20
  comment hello
id 2
  name Hanako
  age 22
id 3
  name Satoru
  age 1
```

#### 2-2. fsmファイル～成功例～
　idを2行書いており違和感があるかもしれませんがこのように記入しないとid配下の項目の2つ目以降を
取得する際に上手くいかなくなります。

```text:fsm_sample_4.textfsm
Value id (\d+)
Value name ([A-Z][a-z]+)
Value age (\d+)
Value comment (\w+)

Start
  ^id \d+ -> Continue.Record
  ^id ${id}
  ^\s+name ${name}
  ^\s+age ${age}
  ^\s+comment ${comment}
```

```yaml:出力
  - age: '20'
    comment: hello
    id: '1'
    name: Taro
  - age: '22'
    comment: ''
    id: '2'
    name: Hanako
  - age: '1'
    comment: ''
    id: '3'
    name: Satoru
```

　データを検索する流れは以下のようになっています。サンプルデータの「comment hello」まで
来た時点で、Taroのデータはそろっているので、次の行の「id 2」に来た際に
__「^id \d+ -> Continue.Record」でデータを出力してから、次の「^id ${id}」でHanakoのデータ取得を
始める__必要があります。なお、__Continue.Recordはデータを出力しつつ次の検索項目へ移動__します。

|サンプルデータの行|fsmファイルでマッチする行|parserが保持している変数|
|---|---|---|
|id 1|^id \d+ -> Continue.Record <br> ^id ${id}|Taroのid|
|　name Taro|^\s+name ${name}|Taroのid/name|
|　age 20|^\s+age ${age}|Taroのid/name/age|
|　comment hello|^\s+comment ${comment}|Taroのid/name/comment|
|id 2|^id \d+ -> Continue.Record <br> ^id ${id}|Hanakoのid|

#### 2-3. fsmファイル～失敗例1～
　「^id ${id} -> Record」のようにidの部分を1行しか書かないと、Taroのid(=1)を取得した時点で
出力してしまい、それ以降のidがずれてしまいます。

```text:fsm_sample_5.textfsm
Value id (\d+)
Value name ([A-Z][a-z]+)
Value age (\d+)
Value comment (\w+)

Start
  ^id ${id} -> Record
  ^\s+name ${name}
  ^\s+age ${age}
  ^\s+comment ${comment}
```

```yaml:出力
  - age: ''
    comment: ''
    id: '1'  # idしか出力していない
    name: ''
  - age: '20'
    comment: hello
    id: '2'  # idがずれている
    name: Taro
  - age: '22'
    comment: ''
    id: '3'  # idがずれている
    name: Hanako
  - age: '1'
    comment: ''
    id: ''  # idがない
    name: Satoru
```

#### 2-4. fsmファイル～失敗例2～
 fsmファイルの最終行にRecordを記入しました。このようにすると、commentがマッチしないデータは
出力されなくなります。各データごとがどのよう出力されるかを以下にまとめておきます。

- 1週目（Taro）
  - commentの行にマッチして正しく出力される
- 2週目（Hanako）
  - commentの行にマッチしないので出力されない
- 2週目（Satoru）
  - commentの行にマッチしないが、最後のデータなので出力される（動作としてはよろしくない）

```text:fsm_sample_6.textfsm
Value id (\d+)
Value name ([A-Z][a-z]+)
Value age (\d+)
Value comment (\w+)

Start
  ^id ${id}
  ^\s+name ${name}
  ^\s+age ${age}
  ^\s+comment ${comment} -> Record
```

```yaml:出力
  - age: '20'
    comment: hello
    id: '1'
    name: Taro
  - age: '1'
    comment: ''
    id: '3'
    name: Satoru
```

### まとめ
　実用的なものを作るとなると、エラー回避を入れたりして作りこむ必要はあると思いますが、
大体の作り方はご理解頂けたのではないでしょうか？

### 関連記事
[networktocode/ntc-templates - GitHub](https://github.com/networktocode/ntc-templates)
[google/textfsm - GitHub](https://github.com/google/textfsm/wiki/TextFSM)
[【Ansible】textfsmのparserは簡単に作れる～基礎編～](https://qiita.com/masa2223/items/611e59f1599ade9e2d1b)