LibXoが組み込まれたコマンドで出力をJSON/XML変換してみる

[FreeBSD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/freebsd) 2日目の記事です。
今日はFreeBSD-11からサポートされている、[LibXo](https://wiki.freebsd.org/LibXo)の紹介をしようと思います。

## LibXoとは？

LibXoはFreeBSD-11以降でサポートされた、任意のデータをテキスト、XML、JSON、HTMLの各フォーマットで出力するライブラリです。アプリケーション側がLibXoでの出力に対応していれば、ユーザ側は自分の扱いやすいフォーマットでデータを処理することが可能になります。

ドキュメントやソースコードは以下のURLで公開されています。GitHubリポジトリを見ると、どうやらJuniperによりメンテナンスされているライブラリのようですね。

 * [LibXo - A Library for Generating Text, XML, JSON, and HTML Output](https://wiki.freebsd.org/LibXo)(FreeBSD Wiki)
 * [libxo(3)](https://www.freebsd.org/cgi/man.cgi?query=libxo&sektion=3&n=1)
 * [https://github.com/Juniper/libxo](https://github.com/Juniper/libxo)

## LibXoが組み込まれているコマンド

### wcコマンドでの例

FreeBSDでは、ベースコマンドのいくつかにLibXoが組み込まれています。例えば、[wc(1)](https://www.freebsd.org/cgi/man.cgi?query=wc&apropos=0&sektion=0&manpath=FreeBSD+13.0-RELEASE+and+Ports&arch=default&format=html)では以下のような形でコマンドの出力を柔軟に変換できます。

```sh
$ cat /etc/os-release
NAME=FreeBSD
VERSION=13.0-RELEASE
VERSION_ID=13.0
ID=freebsd
ANSI_COLOR="0;31"
PRETTY_NAME="FreeBSD 13.0-RELEASE"
CPE_NAME=cpe:/o:freebsd:freebsd:13.0
HOME_URL=https://FreeBSD.org/
BUG_REPORT_URL=https://bugs.FreeBSD.org/
$
$ cat /etc/os-release | wc
       9      10     222
```

`wc` コマンドに `--libxo json,pretty` を指定するとJSONフォーマット、 `--libxo xml,pretty` でXMLフォーマットでの出力になります。

```sh
$ cat /etc/os-release | wc --libxo json,pretty
{
  "wc": {
    "file": [
      {
        "lines": 9,
        "words": 10,
        "characters": 222
      }
    ]
  }
}
$
$ cat /etc/os-release | wc --libxo xml,pretty
<wc>
  <file>
    <lines>9</lines>
    <words>10</words>
    <characters>222</characters>
  </file>
</wc>
```

### libxoが組み込まれているコマンド

`ldd wc` で参照しているライブラリを確認してみると、 `/lib/libxo.so.0` を参照していることが分かります。おそらくこのライブラリを参照しているコマンドは `--libxo json,pretty` のような指定が可能になっているのでしょう。

```sh
$ ldd `which wc`
/usr/bin/wc:
        libxo.so.0 => /lib/libxo.so.0 (0x20444000)
        libcasper.so.1 => /lib/libcasper.so.1 (0x20462000)
        libcap_fileargs.so.1 => /lib/casper/libcap_fileargs.so.1 (0x2046a000)
        libc.so.7 => /lib/libc.so.7 (0x20470000)
        libutil.so.9 => /lib/libutil.so.9 (0x20640000)
        libnv.so.0 => /lib/libnv.so.0 (0x20656000)
```

簡単なスクリプトで `/lib/libxo.so.0` を参照しているコマンドを抽出してみます。

```sh
for i in `echo $PATH | sed -e "s/:/ /g"`
do
  for j in `find $i -type f`
  do
    echo "===> $j"
    ldd $j
  done
done \
| egrep '^===>|libxo' \
| grep -B1 libxo \
| grep '^===>'
```

以下のコマンドが該当するようです。

```
===> /bin/df
===> /bin/ps
===> /sbin/savecore
===> /usr/bin/netstat
===> /usr/bin/w
===> /usr/bin/penv
===> /usr/bin/last
===> /usr/bin/xo
===> /usr/bin/pwdx
===> /usr/bin/pargs
===> /usr/bin/uptime
===> /usr/bin/iscsictl
===> /usr/bin/nfsstat
===> /usr/bin/vmstat
===> /usr/bin/procstat
===> /usr/bin/wc
===> /usr/sbin/lastlogin
===> /usr/sbin/sesutil
===> /usr/sbin/jls
===> /usr/sbin/arp
```

## psコマンドでのサンプル

`/lib/libxo.so.0` を参照しているコマンドには、 `ps` コマンドもあり、より実用的な利用例を示せそうです。
(個人的には `jls` でも `--libxo json,pretty` が利用できるのは嬉しい感じです)

```sh
$ ps ax --libxo json,pretty
{
  "process-information": {
    "process": [
      {
        "pid": "2467",
        "terminal-name": "- ",
        "state": "IsJ",
        "cpu-time": "0:08.29",
        "command": "/usr/sbin/syslogd -s"
      },
      {
        "pid": "2549",
        "terminal-name": "- ",
        "state": "IsJ",
        "cpu-time": "0:00.08",
        "command": "/usr/sbin/sshd"
      },
...
```

`ps ax --libxo json,pretty` での出力を `jq` コマンド＋JSONPathで欲しい項目のみ抽出することが可能になります。
(少なくともシェルスクリプトで細々と文字列処理するよりは楽な感じですね)

```sh
$ ps ax --libxo json,pretty | jq -r '."process-information".process[] | select(.command | startswith("vim"))'
{
  "pid": "48072",
  "terminal-name": "12 ",
  "state": "TJ",
  "cpu-time": "0:00.10",
  "command": "vim -R wc.c"
}
$
$ ps ax --libxo json,pretty | jq -r '."process-information".process[] | select(.command | startswith("vim")) | .pid, .command'
48072
vim -R wc.c
```

## まとめ

FreeBSD-11からサポートされた、[LibXo](https://wiki.freebsd.org/LibXo)について紹介しました。
ベースコマンドのいくつかはLibXoを組み込んだ状態で提供されており、 `--libxo json,pretty` のような指定でJSON,XML等の任意の形式で出力を得られます。JSONやXMLは `jq` や `xmllint` 等でのパースや条件にマッチする項目の抽出が柔軟に行えるため、シェルスクリプトにおける可読性やメンテナンス性の向上に役立ちそうです。