シェル芸で使いたい jqイディオム

# はじめに
このページは JSONのデータを変換処理するコマンドラインツール [jq (http://stedolan.github.io/jq/)](http://stedolan.github.io/jq/) を、[シェル芸](https://blog.ueda.asia/?page_id=1434)で上手く使うためのテクニックをまとめたページです。

jqのインストール方法については、[Qiita](http://qiita.com/tags/jq) や [インターネット上のページ](https://www.google.co.jp/?gfe_rd=cr&ei=kk4jV-XFMu7Z8AeF5amACQ&gws_rd=ssl#q=jq+%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) を参考にして下さい。

[第22回シェル芸勉強会 大阪サテライト](https://5f01b3bc1d81c1fae2378cdc89.doorkeeper.jp/events/42550) の [LTで発表した内容](https://dl.dropboxusercontent.com/u/54939588/slides/UsefulJqIdiomInShellGei.html) に加筆・修正を行ったものとなっております。

# 定番のヘルプ表示
```
$ jq -h
jq - commandline JSON processor [version 1.5]
Usage: jq [options] <jq filter> [file...]

        jq is a tool for processing JSON inputs, applying the
        given filter to its JSON text inputs and producing the
        filter's results as JSON on standard output.
        The simplest filter is ., which is the identity filter,
        copying jq's input to its output unmodified (except for
        formatting).
        For more advanced filters see the jq(1) manpage ("man jq")
        and/or https://stedolan.github.io/jq

        Some of the options include:
         -c             compact instead of pretty-printed output;
         -n             use `null` as the single input value;
         -e             set the exit status code based on the output;
         -s             read (slurp) all inputs into an array; apply filter to it;
         -r             output raw strings, not JSON texts;
         -R             read raw strings, not JSON texts;
         -C             colorize JSON;
         -M             monochrome (don't colorize JSON);
         -S             sort keys of objects on output;
         --tab  use tabs for indentation;
         --arg a v      set variable $a to value <v>;
         --argjson a v  set variable $a to JSON value <v>;
         --slurpfile a f        set variable $a to an array of JSON texts read from <f>;
        See the manpage for more options.
```

# カラー表示にする起動オプション
```bash
$ aws ec2 describe-instances | jq -C '.' | less -R
```
- シェルのプロファイルで alias jqc='jq -C' とか export LESS='-R' とかしておくと良いかもしれないです。
- サンプルデータ生成に [AWS CLI](https://aws.amazon.com/jp/cli/) のコマンドを使用するので、AWS CLIをセットアップするか、ダミーデータ [aws-ec2-describe-instances.json](https://dl.dropboxusercontent.com/u/54939588/slides/aws-ec2-describe-instances.json) をダウンロードして cat するかして下さい

# JSONの抽出
```bash
$ aws ec2 describe-instances | jq '.'
$ aws ec2 describe-instances | jq '.Reservations'
$ aws ec2 describe-instances | jq '.Reservations[]'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]'
```
- . は入力内容そのものを表すフィルタ、jq界隈ではセレクタではなくフィルタという
- [n] や [m,n] など [l:n] で配列の要素取得、[]は特殊で配列の全ての要素を取り出す

# JSONの再構築
```bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | .InstanceId'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | [ .InstanceId, .InstanceType, .State.Name ]'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | { InstanceId: .InstanceId, InstanceType: .InstanceType }'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | { InstanceId, InstanceType }' #上記の短縮形
```
- jqフィルタ内で | を使用すると、以降の . がパイプへの入力データに置き変わっている
- jqフィルタ内で [[ ... ] や { ... } を使用すると、配列やオブジェクトを生成](https://stedolan.github.io/jq/manual/#TypesandValues)できる

# 結果の確認方法
```bash
$ aws ec2 describe-instances | jq '.Reservations | type'
$ aws ec2 describe-instances | jq '.Reservations | length'
$ aws ec2 describe-instances | jq '.Reservations | keys'
```
- type, length, keys関数で入力データのデータ型、配列個数、オブジェクトのキー名が取得できます
- これで確認すると jqのフィルタの | がオブジェクトパイプラインなのだと分かります

# JSONの抽出2
```bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | [ .InstanceId, .Tags[]? ]'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | [ .InstanceId, (.Tags[]? | select(.Key == "Name")) ]'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | [ .InstanceId, (.Tags[]? | select(.Key == "Name")).Value ]'
```
- key/value用に from_entries関数がありますが、一つでも要素が抜けているとエラーになるので、[フィルタの後ろに ? を付けてエラーを無視する指定](https://stedolan.github.io/jq/manual/#Basicfilters)を行っています
 
# JSONの置換
```bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | .InstanceId = .InstanceId + "-xxx"'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | .InstanceId |= . + "-xxx"'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] |= . + {"newKey": "newValue"}'
$ aws ec2 describe-instances | jq '.Reservations[].Instances[].newKey |= "newValue"'
```
- =代入すると、JSON内容の一部置換が行える。 . で入力されたJSON内容の参照が可能
- |=代入も同様にJSON内容の一部置換だが . での参照が左辺で指定したJSON内容となる
- オブジェクトへのキー追加はオブジェクト同士の加算をするか、新規キーへの代入で行う
- 他にもいろいろ[略記方法](https://stedolan.github.io/jq/manual/#Assignment)が存在する
 
# JSONの置換2
```bash
$ echo '[1,2,3,4,5]' | jq 'map(. * 2)'
$ echo '{"a":1, "b":2, "c":3, "d":4, "e":5}' | jq 'map_values(. + 1)'
$ echo '{"a":1, "b":2, "c":3, "d":4, "e":5}' | jq 'to_entries'
$ echo '{"a":1, "b":2, "c":3, "d":4, "e":5}' | jq 'to_entries | from_entries'
$ echo '{"a":1, "b":2, "c":3, "d":4, "e":5}' | jq 'with_entries(.key += "z" | .value += 1)'
```
- map関数は配列の各要素に指定したフィルタを適用する。 map(x) は [.[] | x] と同意
- オブジェクトの単純操作の場合は map_values関数が使える。 map_values(x) は .[] |= x と同意
- キー、値を利用するオブジェクト操作は to_entries, from_entries, with_entries関数を利用する
- to_entries関数はオブジェクト {"k": "v"} を {"key": "k", "value": "v"} の形式に変換する
- from_entries関数は上記の逆変換、with_entries(x) は to_entries | map(x) | from_entries と同意

# 結果の整形
```bash
$ aws ec2 describe-instances | jq -c '.Reservations[].Instances[] | [ .InstanceId, .InstanceType, .State.Name ]'
$ aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | [ .InstanceId, .InstanceType, .State.Name ] | @csv'
$ aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | [ .InstanceId, .InstanceType, .State.Name ] | @tsv'
$ aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | [ .InstanceId, .InstanceType, .State.Name ] | @sh'
```
- -c　--compact-output　改行を減らしたコンパクトな出力形式
- -r　--raw-output　　　 文字列の引用符を外す
- @フォーマット名で出力形式の指定や、文字エスケープの指定を行う事ができる

# あとは流れでシェル芸に
```bash
$ aws ec2 describe-instances \
  | jq -r '.Reservations[].Instances[] | select(.State.Name == "stopped") | [ .InstanceId, (.Tags[]? | select(.Key == "Name")).Value ] | @sh' \
  | awk '{ print "echo Instance Starting... " $1 " " $2 "; aws ec2 start-instances --instance-ids " $1 }' | sh
```
AWSのEC2インスタンス一覧から停止状態のものだけを抽出し、インスタンスIDとNameタグだけ awkに渡して
個別にエコー表示、インスタンスを起動するコマンドの文字列を生成して、それをシェルに渡すといったような事をやってます

# CSVの読み込み
```bash
$ cat sample.csv | jq -R 'split(",") | {"fld1": .[0], "fld3": .[2]}'
```
- -R　--raw-input　各行を文字列として読み込んで、後続のフィルタに渡す。単一の文字列として読み込みたい場合は -s --slurp をあわせて指定する 

# CSVの書き出し(ヘッダ先頭行付き)
```bash
$ echo '[{"a":1,"c":2,"b":3},{"a":4,"c":5,"b":6}]' | jq -r  '(.[0]|keys_unsorted),map([.[]])[]|@csv'
$ echo '{"a":1,"c":2,"b":3}{"a":4,"c":5,"b":6}'    | jq -rs '(.[0]|keys_unsorted),map([.[]])[]|@csv'
$ alias jqhcsv="jq -rs '(.[0]|keys_unsorted),map([.[]])[]|@csv'"
$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | { InstanceId, InstanceType }' | jqhcsv
```
- 作成元JSONが配列の場合は最初の要素のキー名配列のヘッダ部分とハッシュを配列化したデータ部分を合わせた配列を作成すれば良い
- AWS CLI等の出力で配列でない場合は -sオプションで配列にしてから、上記と同じ処理
- ヘッダ付きCSV変換処理をエイリアス登録しておくと、最後のコマンドの様に実行できて便利

# 引数の変数展開
```bash
$ jq -n --arg a 1 --arg b 2 --arg c 3 '{ a: $a, b: $b, c: $c }'
```
- -n　--null-input　入力データ無しを許可して、jqフィルタを一回だけ実行する
- --argオプションで設定した変数名、変数値を jqフィルタ内で $変数名で参照している

# 式の変数化
```bash
$ echo '{"a":[1,2],"b":[3,4],"c":[5,6]}' | jq '.a as $a|.c as $c| .b | $c + . + $a'
```
- 式の後ろに " as $変数名" を付加すると、コンテキストはそのままで、式を変数化し、後続の処理で参照が可能となる

# 文字列内の式展開
```bash
$ echo '[2,3,5,7,11]' | jq '"first \(.[0])", "second \(.[1])"'
```
- jqフィルタの文字列内で \\(フィルタ) と記述すると、フィルタの実行結果を文字列に展開できる

# その他、使えそうな[組み込み関数](https://stedolan.github.io/jq/manual/#Builtinoperatorsandfunctions)
|関数名|機能|
|:----|:---|
|sort, sort_by|入力配列をソートする|
|unique, unique_by　　|入力配列の重複を削除する|
|group_by|入力配列をグループ化する|
|add|配列の各要素を数値の場合は合算、文字列は結合　　|
|range|指定範囲の数値群を生成する|

#参考資料
- [jqを活用してAPIレスポンス等から欲しい情報だけを抽出する【初級編】](http://dev.classmethod.jp/tool/road-to-jq-master-novice/)
- [jqを活用してAPIレスポンス等から欲しい情報だけを抽出する【中級編】](http://dev.classmethod.jp/tool/road-to-jq-master-apprentice/)
- [jqを活用してAPIレスポンスから欲しい情報を抽出する【上級編】](http://dev.classmethod.jp/tool/road-to-jq-master-journeyman/)
- [jq コマンドが強力すぎてヤバい件](http://momijiame.tumblr.com/post/51299899637/jq-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%8C%E5%BC%B7%E5%8A%9B%E3%81%99%E3%81%8E%E3%81%A6%E3%83%A4%E3%83%90%E3%81%84%E4%BB%B6)
- [jqでcsvをjsonに変換する](http://qiita.com/shobyshoby/items/68204f8567efbd32d705)
- [jq コマンドを使う日常のご紹介](http://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af)
- [軽量JSONパーサー『jq』のドキュメント：『jq Manual』をざっくり日本語訳してみました](http://dev.classmethod.jp/tool/jq-manual-japanese-translation-roughly/)
- [Just another Ruby porter, 2016-06-13 (Mon)](http://jarp.does.notwork.org/diary/201606b.html#20160613)
- [Just another Ruby porter, 2016-06-14 (Tue)](http://jarp.does.notwork.org/diary/201606b.html#20160614)
- [Just another Ruby porter, 2016-06-15 (Wed)](http://jarp.does.notwork.org/diary/201606b.html#20160615)
- [jq Primer: Munging JSON Data](http://andrew.gibiansky.com/blog/command-line/jq-primer/)