jqのむだづかいー検索篇

[JSON](https://datatracker.ietf.org/doc/html/rfc8259)処理のコマンドラインツールである[jq](https://stedolan.github.io/jq/)は、JSONテキストを1行でちゃくっと解析、変換するときに使うものです。複雑なことをするときは、`sed`や`awk`など他のユーティリティを併用します。

とは言え、`jq`にも変数定義などプログラミング言語らしき機能が備わっているので、複雑なことも「やればできんじゃねぇ?」と野望を募らせてしまうこともあります。

ここでは、次のようなオブジェクトを要素とした配列から、指定の部分文字列に`Name`の値がマッチするオブジェクトを抽出する`jq`フィルタを考えます。

```json
[
 {
  "id": 2,
  "Name": "Cumings, Mrs. John Bradley (Florence Briggs Thayer)",
  "Sex": "female",
  "Age": 38,
  "Ticket": "PC 17599",
  "Fare": 71.2833,
  "Cabin": "C85"
 },
 ⋮
]
```

以下、入力データはファイル`search-sample.json`に収容されているとします。

単純なマッチなら簡単です。`contains`から指定のキー:値のプロパティが含まれているか（値は部分文字列でよい）の真偽値を得て、それをベースに`select`で真のオブジェクトだけを抜き出せばよいのです。

```bash
$ cat search-sample.json | jq --arg value Laina '.[] | select(contains({"Name": $value}))'
{
  "id": 3,
  "Name": "Heikkinen, Miss. Laina",
  "Sex": "female",
  "Age": 26,
  "Ticket": "STON/O2. 3101282",
  "Fare": 7.925,
  "Cabin": ""
}
```

これが、値の大文字小文字に関わらずに検索せよとなると、多少ややこしくなります。私が頂いたオリジナルのお題は「Javaで書け」でしたが、相手はJSONテキストです。`jq`でできないはずはないじゃないですか（というか、多分、`jq`の方が楽だと思う）。

### コード

コード（フィルタファイル）は`search.jq`で、次の[Github](https://github.com/stoyosawa/jqDoc-public/tree/main/filters)から取得できます（短いのでコピペでも十分ですが）。

```https://github.com/stoyosawa/jqDoc-public/```

フィルタファイルの改行はUnixスタイルの`LF`だけでなければならないので、保存時に注意してください。Windows流に`CRLF`だと次のエラーが報告されます。

```
jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?)
  at <top-level>, line 1:
```


### フィルタファイルの使い方

これらファイルを`jq`から呼び出すときは、次のオプションを指定します。

- `--arg`オプションー検索部分文字列を指定します。ここでは変数名を`value`としたので、`--arg value 文字列`となります。
- `-f`オプション（`--from-file`）ーフィルタファイルを読み込みます。

まとめると、次のようになります（検索文字列はlaina）。

```bash
$ jq --arg value laina -f search.jq search-sample.json
```

### 大文字小文字を区別しないフィルタリング

フィルタは次の通りです。

```
($value | ascii_downcase) as $value |               # 変数 value を小文字化
.[] |                                               # オブジェクト単位のループ
.name = (.Name | ascii_downcase) |                  # 小文字版のname属性を加える
select(contains({"name": $value})) |                # nameをベースにフィルタ
del(.name)                                          # nameを削除
```

最初の大文字小文字を区別するワンライナーとの違いは、変数`value`とプロパティ`Name`の値を、どちらも`ascii_downcase`で小文字化しているところです（1、3行目）。そして、この小文字化された値を新しいプロパティ`name`としてオブジェクトに加えます（3行目）。

これで、`contains`を用いた内包の真偽判定で`name`の方を対象にフィルタリングすれば、大文字小文字を区別しない検索となります。

最後に一時的に用意した`name`を`del`から削除します。

これで上がりです。

実行例を示します。検索文字列が小文字のlainaでもLainaが引っかかることがわかります。

```bash
$ cat search-sample.json | jq --arg value lai -f search.jq
{
  "id": 3,
  "Name": "Heikkinen, Miss. Laina",
  "Sex": "female",
  "Age": 26,
  "Ticket": "STON/O2. 3101282",
  "Fare": 7.925,
  "Cabin": ""
}
```

### おわりに

実用性のない、お遊びな`jq`フィルタを追及していますが、この回は、実用性ありですね。残念。でも、Unixユーザなら[`egrep`](https://linuxjm.osdn.jp/html/GNU_grep/man1/grep.1.html)を使います、きっと。

```
$ cat search-sample.json | jq -c '.[]' | egrep -i '"Name":".+laina' | jq '.'
```

ポイントは、`-c`オプションで複数のプロパティからなるオブジェクトを1行にまとめることで、行志向の`grep`でオブジェクト全体を引っかけられるようにしているところです。末尾の`jq`はこの1行化されたオブジェクトを、読みやすいように1プロパティ1行に戻しています。正規表現は、これだと`Name`以降のプロパティ値にLainaがあったときにも引っかかってしまいます（たとえば "Ticket": "Laina"）ので、再考の余地があります。


### 参考

- [./jq](https://stedolan.github.io/jq/) - `jq`のオフィシャルサイトです（英文）。
- [man page for GREP](https://linuxjm.osdn.jp/html/GNU_grep/man1/grep.1.html) - Unixコマンドの`egrep`のman pageです。
- [jqマニュアル（開発バージョン）](https://yujiorama.github.io/unofficial-translations/jq/l10n/jp/manual/) - Yuji Okazawa氏が（非公式に）訳しておられる`jq`マニュアルの日本語版です。和訳マニュアルは他にもあるので、検索してください。
- [The JavaScript Object Notation (JSON) Data Interchange Format（RFC 8259）](https://www.rfc-editor.org/rfc/rfc8259.txt) - JSONの仕様です（英文）。しばしば[RFC 7159](https://www.rfc-editor.org/rfc/rfc7159.txt)が参照されますが、そちらはobsoleteになりました。
- [JSON(JavaScript Object Notation)データ交換フォーマット](https://www.asahi-net.or.jp/~ax2s-kmtn/internet/rfc8259j.html) - 上記の非公式の和訳です。
- [jqハンドブックーNetOps/DevOps必携のJSONパーザ](http://www.cutt.co.jp/book/978-4-87783-491-3.html) - `contain`や`select`はこちらでも取り上げています。【[出版社](http://www.cutt.co.jp/book/978-4-87783-491-3.html) | [honto](https://honto.jp/netstore/pd-book_31037910.html) | [amazon.co.jp](https://www.amazon.co.jp/jq/dp/4877834915) | [ヨドバシカメラ](https://www.yodobashi.com/product/100000009003450000/)】。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/20272c85-f084-557b-189f-aa7128e283ab.png)