LibXoを利用するメリットとJSONとjqによるペナルティ

# はじめに

@furandon_pig さんが投稿した[LibXoの紹介記事](https://qiita.com/furandon_pig/items/28b4d83e24dfd4dda115)に触発されて、自分が以前から使っていたプログラム(シェルスクリプト)を、LibXoを利用して書き直してみました。

# arpの出力を見やすくする

IPv4でMACアドレスとIPアドレスの対応を管理するのがARP (Address Resolution Protocol)です。ここではそのプロトコルの詳細は割愛しますが、ホストがどんなIPアドレスとMACアドレスの対応(arpテーブルやarpキャッシュと呼ばれる)を持っているかの確認やarpの登録・削除を行うコマンドがarpコマンドです。

このホストのigb1側のarpテーブルは、次のようになっています。

```
$ arp -a -i igb1
idmdeb10.example.jp (172.16.32.12) at 00:a0:98:3b:1d:49 on igb1 expires in 1100 seconds [ethernet]
devdeb10.example.jp (172.16.32.13) at 00:a0:98:85:07:c6 on igb1 expires in 1030 seconds [ethernet]
echo-hogehoge.example.jp (172.16.32.8) at 0c:47:c9:49:11:ed on igb1 expires in 1161 seconds [ethernet]
fs.example.jp (172.16.32.10) at 70:85:c2:a2:f1:83 on igb1 expires in 410 seconds [ethernet]
idmbx1.example.jp (172.16.32.11) at 00:05:1b:24:26:c1 on igb1 expires in 1116 seconds [ethernet]
media-tablet.example.jp (172.16.32.7) at d0:65:ca:a7:bf:10 on igb1 expires in 1046 seconds [ethernet]
sh.example.jp (172.16.32.1) at 00:60:e0:5a:74:1b on igb1 permanent [ethernet]
resolver.example.jp (172.16.32.2) at 00:60:e0:5a:74:1b on igb1 permanent [ethernet]
$
```
arpコマンドの出力はカラムが揃っていないためあまり見やすいとは言えません。

これを見やすくするためにsedとsortコマンドを組み合わせて、自分で以前から利用しているのが次のコマンド(シェルスクリプトです)。

```
$ cat my-arp
#!/bin/sh

IF=igb1

arp -a -i "${IF}" |
  sed 's/\([^ ]*\) (\([^)]*\)) at \(.*\) on.*/\2\t\3\t\1/' |
    sort --version-sort

$ ./my-arp
172.16.32.1     00:60:e0:5a:74:1b       sh.example.jp
172.16.32.2     00:60:e0:5a:74:1b       resolver.example.jp
172.16.32.7     d0:65:ca:a7:bf:10       media-tablet.example.jp
172.16.32.8     0c:47:c9:49:11:ed       echo-hogehoge.example.jp
172.16.32.10    70:85:c2:a2:f1:83       fs.example.jp
172.16.32.11    00:05:1b:24:26:c1       idmbx1.example.jp
172.16.32.12    00:a0:98:3b:1d:49       idmdeb10.example.jp
172.16.32.13    00:a0:98:85:07:c6       devdeb10.example.jp
$ 
```

arpコマンドの出力そのままに比べてかなり見やすくなっています。


# --libxo jsonオプションとjqを使って書き直してみる

それでは前述のコマンドをarpに組み込まれているLibXoを使って出力をJSONフォーマットに変更し、JSONの出力をjqコマンド(pkgにあります)で必要な項目を拾い出して結果を整えてみましょう。

まず arp コマンドで `—-libxo json` を使った時にどのようなjson出力になるかを確認します。

```
$ arp --libxo json,pretty -a -i igb1 
{
  "__version": "1", 
  "arp": {
    "arp-cache": [
      {
        "hostname": "idmdeb10.example.jp",
        "ip-address": "172.16.32.12",
        "mac-address": "00:a0:98:3b:1d:49",
        "interface": "igb1",
        "expires": 1174,
        "type": "ethernet"
      },
 —- 以下省略 —-
```

この通り `"arp"`内の `"arp-cache[]"` という配列に各arpテーブルの情報が格納されています。my-arpとして必要なのは `"hostname"`, `"ip-address"`, `"mac-address"` なので、これを jq を使って抜き出し、出力をタブ区切りでフォーマットします。

コマンドを書き換えると次のようになります。

```
$ cat my-arp-new
#!/bin/sh

IF=igb1

arp -a -i "${IF}" --libxo json |
  jq -r '.arp."arp-cache"[] | [."ip-address", ."mac-address", .hostname] | @tsv' |
    sort --version-sort
$
```
sedを使った正規表現による置換に比べて、JSONフォーマットとjqを使うとどの項目を拾い上げているのかがよくわかります。またJSONとjqのおかげでメンテナンス性が大きく向上し、項目の追加、削除もとても簡単にできるのがわかります。

実際にこのコマンドを使うと結果は次のようになります。

```
$ ./my-arp-new
172.16.32.1     00:60:e0:5a:74:1b       sh.example.jp
172.16.32.2     00:60:e0:5a:74:1b       resolver.example.jp
172.16.32.7     d0:65:ca:a7:bf:10       media-tablet.example.jp
172.16.32.8     0c:47:c9:49:11:ed       echo-hogehoge.example.jp
172.16.32.10    70:85:c2:a2:f1:83       fs.example.jp
172.16.32.11    00:05:1b:24:26:c1       idmbx1.example.jp
172.16.32.12    00:a0:98:3b:1d:49       idmdeb10.example.jp
172.16.32.13    00:a0:98:85:07:c6       devdeb10.example.jp
$ 
```


# JSONとjqによるペナルティ

以上の通りでLibXoの活用でプログラムがわかりやすくなってメデタシメデタシといきたいところですが、ここにはわずかなペナルティがあります。それはjqの処理があまり速く無いことです。実際にtimeを使ってコマンドの実行時間を確認してみます。

```
$ time ./my-arp > /dev/null
        0.02 real         0.01 user         0.01 sys

$ time ./my-arp-new > /dev/null
        0.08 real         0.07 user         0.01 sys
```
この通りsedを使ったものでは0.02秒で処理できたものが、JSONとjqの組み合わせにより0.08秒となりました。

arpコマンドの内部では通常のテキスト形式の出力であってもLibXoを使っていますので `—-libxo json` による処理時間の増加は誤差であり、時間の増加は主にjqの処理に依存していると考えて良いでしょう。この程度のプログラムであれば処理時間の増加は気にしなくても構わないわけですが、出力量が増えた時は問題になるかもしれません。

また出力形式がJSONになることによりデータ量が増えるとことも考慮する必要があります。

```
$ arp -a -i igb1 | wc -c
     793

$ arp -a -i igb1 --libxo json | wc -c
    1278
```


# まとめ

LibXoを使ってjsonで出力すると後の処理は簡単になりますが、計算量(処理量)とデータ量は増えるため、状況によってはテキスト出力をそのままsed等で扱ったほうが高速に処理できる場合もあることを覚えておきましょう。