【Linux】【Windows】jq コマンド ~ JSON を扱う ~

■ はじめに

AWS CLI からのレスポンスがJSONなので
そこから値を取得する必要がある。
そこで調べてみたら jqコマンド ってのがみつかったのでメモ。

目次

【1】参考にできるサイト
 1)ドキュメント
 2)動画
【2】環境設定
 1)Linux の場合
 2)Windows の場合
【3】コマンドオプション
 1)-r オプション
 2)-f オプション
【4】使用上の注意
 1)jq -f <file>で行う場合、改行コードはLFにしておくこと
【5】基本的な文法
 1)「.」
 2)SELECT
 3)map
 4)if-then-else
 5)変数定義(as)
 6)代替演算子(//)
【6】サンプル
 例1:Windows環境下での実行
 例2:Linux環境下での実行
 例3:JSON の扱い
 例4:複雑なJSON

【1】参考にできるサイト

1)ドキュメント

https://jqlang.github.io/jq/manual/

2)動画

https://dotinstall.com/lessons/basic_jq

【2】環境設定

1)Linux の場合

yumコマンド

sudo yum -y install epel-release
sudo yum -y install jq

2)Windows の場合

[1] 以下のサイトから、「jq-win64.exe」をダウンロードし、
 任意の場所に置く

https://stedolan.github.io/jq/

[2] コマンドプロンプトを立ち上げて、[1]の場所まで移動
[3] 「jq-win64.exe --version」を入力
 => 今回は「jq-1.6」が出力される

補足:任意設定

* 以下のようにしておくと、Linuxライクに使える

[1] 「jq-win64.exe」を「jq.exe」にリネーム
[2] 環境変数 Path にjq.exeまでのパスを追加(例:Path=C:\soft\)
[3] コマンドプロンプトを立ち上げて、「jq --version」を入力
 => 今回は「jq-1.6」が出力される

【3】コマンドオプション

1)-r オプション

囲み文字であるダブルクォートが除去

2)-f オプション

* フィルタ情報をファイルで指定 (-f | --from-file <file>)
 => JSONファイルから別形式のJSONが出力した時などに使える

https://www.tohoho-web.com/ex/jq.html#opt-from-file

例: JSON to another JSON

* 以下のサイトで行われていることを「-f」オプションを使ってみる

https://www.digitalocean.com/community/tutorials/how-to-transform-json-data-with-jq

# [1] まずは、データの確認
less seaCreatures.json
~~~~
[                                                           
  { "name": "Sammy", "type": "shark", "clams": 5 },
  { "name": "Bubbles", "type": "orca", "clams": 3 },
  { "name": "Splish", "type": "dolphin", "clams": 2 },
  { "name": "Splash", "type": "dolphin", "clams": 2 }
]
~~~~

# [2] 同じことをやってみる(JSONファイルから別形式のJSONが出力された)
jq '{ creatures: map(.name), totalClams: map(.clams) | add, totalDolphinClams: map(select(.type == "dolphin").clams) | add }' seaCreatures.json
~[出力結果]~~~
{
  "creatures": [
    "Sammy",
    "Bubbles",
    "Splish",
    "Splash"
  ],
  "totalClams": 12,
  "totalDolphinClams": 4
}
~~~~

# [3] 「{ creatures: ... add }'」部分をファイル化する
vi template.jq
~~~~
{ creatures: map(.name), totalClams: map(.clams) | add, totalDolphinClams: map(select(.type == "dolphin").clams) | add }
~~~~

# [4] [2] と同じ結果になることを確認する
jq -f template.jq seaCreatures.json
~[出力結果]~~~
{
  "creatures": [
    "Sammy",
    "Bubbles",
    "Splish",
    "Splash"
  ],
  "totalClams": 12,
  "totalDolphinClams": 4
}
~~~~~~

【4】使用上の注意

1)jq -f で行う場合、改行コードはLFにしておくこと

https://dk521123.hatenablog.com/entry/2024/04/19/121312

でやらかしたのだが、jq -f <file>を使う場合、ファイル(<file>)の改行コードは、
CRLFではなくLFにしておくこと
 => Windows環境下だけなら、気にしなくていいが、LFで統一した方が無難
 => 以下「エラーメッセージ」のようになってしまう可能性がある

エラーメッセージ

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

【5】基本的な文法

1)「.」

* 「現在の入力」を表す

2)SELECT

* 値の抽出

$ echo '[{"name": "_Hello", "value": 25},{"name": "World", "value": 30}]' | jq '.[] | select(.name | startswith("_"))'
{
  "name": "_Hello",
  "value": 25
}

# 逆
$ echo '[{"name": "_Hello", "value": 25},{"name": "World", "value": 30}]' | jq '.[] | select(.name | startswith("_") | not)'
{
  "name": "World",
  "value": 30
}

3)map

* 配列やオブジェクトを受け取り、配列を返却

$ echo '[1,2,3]' | jq -c 'map(. * 2)'
[2,4,6]

4)if-then-else

* If 文で制御できる

https://jqlang.github.io/jq/manual/#if-then-else-end

5)変数定義(as)

* 変数を定義する

. as $file

$file.violations[] as $violation

6)代替演算子(//)

* 値がない時の代替えするための演算子

# x が false や null でなければ x、さもなくば y
x // y

. // {}

【6】サンプル

例1:Windows環境下での実行

aws ssm get-parameters のレスポンスをパースする

https://docs.aws.amazon.com/cli/latest/reference/ssm/get-parameters.html

のレスポンスを使う

コマンド例

$ type sample.json | jq-win64.exe  .Parameters[].Value
"good day sunshine"

sample.json

{
  "Parameters": [
      {
          "Name": "helloWorld",
          "Type": "String",
          "Value": "good day sunshine",
          "Version": 1,
          "LastModifiedDate": 1542308384.49,
          "ARN": "arn:aws:ssm:us-east-1:123456789012:parameter/helloWorld"
      }
  ],
  "InvalidParameters": []
}

例2:Linux環境下での実行

https://dk521123.hatenablog.com/entry/2020/04/16/113816

にあるような開発環境下によって取得する値を変える

コマンド例

$ cat s3-setting.json | jq '.dev[]'
"s3-bucket-dv001"
"s3-bucket-dv002"
"s3-bucket-dv003"

$ cat s3-setting.json | jq -r '.prod.s3_bucket2'
s3-bucket-pd002

s3-setting.json

{
  "prod": {
    "s3_bucket1":"s3-bucket-pd001",
    "s3_bucket2":"s3-bucket-pd002",
    "s3_bucket3":"s3-bucket-pd003"
  },
  "stage": {
    "s3_bucket1":"s3-bucket-sg001",
    "s3_bucket2":"s3-bucket-sg002",
    "s3_bucket3":"s3-bucket-pd003"
  },
  "dev": {
    "s3_bucket1":"s3-bucket-dv001",
    "s3_bucket2":"s3-bucket-dv002",
    "s3_bucket3":"s3-bucket-dv003"
  }
}

例3:JSON の扱い

* 最終的には、以下「input.json」の
 「CRITICAL」又は「HIGH」の合算値を集計。
 「CRITICAL」、「HIGH」がなかったら、「0」を返す

input.json

{
  "CRITICAL": 1,
  "HIGH": 2,
  "MEDIUM": 3,
  "LOW": 4,
  "UNTRIGED": 5
}

コマンド例

$ cat input.json | jq 'keys'
[
  "CRITICAL",
  "HIGH",
  "LOW",
  "MEDIUM",
  "UNTRIGED"
]

$ cat input.json |  jq '. | to_entries'
[
  {
    "key": "CRITICAL",
    "value": 1
  },
  {
    "key": "HIGH",
    "value": 2
  },
  {
    "key": "MEDIUM",
    "value": 3
  },
  {
    "key": "LOW",
    "value": 4
  },
  {
    "key": "UNTRIGED",
    "value": 5
  }
]

$ cat input.json | jq '. | to_entries[] | select(.key=="CRITICAL" or .key=="HIGH")'
{                                                                                                 
  "key": "CRITICAL",                                                                              
  "value": 1                                                                                      
}                                                                                                 
{                                                                                                 
  "key": "HIGH",                                                                                  
  "value": 2                                                                                      
}                                                                                                 

$ cat input.json | jq -c '[. | to_entries[] | select(.key=="CRITICAL" or .key=="HIGH") | .value] | add'
3

# データを変更
$ vi input2.json
~~~~
{
 "MEDIUM": 3,
 "LOW": 4,
 "UNTRIGED": 5
}
~~~~

$ cat input2.json | jq -e '[. | to_entries[] | select(.key=="CRITICAL" or .key=="HIGH") | .value] | add'
null

# 代替演算子(//)
$ cat input.json | jq -e '[. | to_entries[] | select(.key=="CRITICAL" or .key=="HIGH") | .value] | add | . // 0'
3
# null の時は、0に置き換える
$ cat input2.json | jq -e '[. | to_entries[] | select(.key=="CRITICAL" or .key=="HIGH") | .value] | add | . // 0'
0

https://orebibou.com/ja/home/201605/20160509_001/
代替演算子(//)
https://www.tohoho-web.com/ex/jq.html

x // y     # x が false や null でなければ x、さもなくば y

例4:複雑なJSON

https://dk521123.hatenablog.com/entry/2023/06/12/000000

で扱ったJSONをパースする

コマンド例 (Windows)

# ex1
$ type sql.json | jq .[] | jq .CreateTable.name
[
  {
    "value": "new_table",
    "quote_style": null
  }
]

# ex2
$ type sql.json | jq .[] | jq .CreateTable.name[].value
"new_table"

# ex3
$ type sql.json | jq .[] | jq -r .CreateTable.name[].value
new_table

sql.json

[
  {
    "CreateTable": {
      "or_replace": false,
      "temporary": false,
      "external": false,
      "global": null,
      "if_not_exists": false,
      "transient": false,
      "name": [
        {
          "value": "new_table",
          "quote_style": null
        }
      ],
      "columns": [],
      "constraints": [],
      "hive_distribution": "NONE",
      "hive_formats": {
        "row_format": null,
        "storage": null,
        "location": null
      },
      "table_properties": [],
      "with_options": [],
      "file_format": null,
      "location": null,
      "query": {
        "with": null,
        "body": {
          "Select": {
            "distinct": null,
            "top": null,
            "projection": [
              {
                "UnnamedExpr": {
                  "CompoundIdentifier": [
                    {
                      "value": "t1",
                      "quote_style": null
                    },
                    {
                      "value": "id",
                      "quote_style": null
                    }
                  ]
                }
              },
              {
                "UnnamedExpr": {
                  "CompoundIdentifier": [
                    {
                      "value": "t1",
                      "quote_style": null
                    },
                    {
                      "value": "code",
                      "quote_style": null
                    }
                  ]
                }
              },
              {
                "UnnamedExpr": {
                  "CompoundIdentifier": [
                    {
                      "value": "t2",
                      "quote_style": null
                    },
                    {
                      "value": "name",
                      "quote_style": null
                    }
                  ]
                }
              }
            ],
            "into": null,
            "from": [
              {
                "relation": {
                  "Table": {
                    "name": [
                      {
                        "value": "table_1",
                        "quote_style": null
                      }
                    ],
                    "alias": {
                      "name": {
                        "value": "t1",
                        "quote_style": null
                      },
                      "columns": []
                    },
                    "args": null,
                    "with_hints": []
                  }
                },
                "joins": [
                  {
                    "relation": {
                      "Table": {
                        "name": [
                          {
                            "value": "table_2",
                            "quote_style": null
                          }
                        ],
                        "alias": {
                          "name": {
                            "value": "t2",
                            "quote_style": null
                          },
                          "columns": []
                        },
                        "args": null,
                        "with_hints": []
                      }
                    },
                    "join_operator": {
                      "LeftOuter": {
                        "On": {
                          "BinaryOp": {
                            "left": {
                              "CompoundIdentifier": [
                                {
                                  "value": "t2",
                                  "quote_style": null
                                },
                                {
                                  "value": "id",
                                  "quote_style": null
                                }
                              ]
                            },
                            "op": "Eq",
                            "right": {
                              "CompoundIdentifier": [
                                {
                                  "value": "t1",
                                  "quote_style": null
                                },
                                {
                                  "value": "id",
                                  "quote_style": null
                                }
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                ]
              }
            ],
            "lateral_views": [],
            "selection": {
              "BinaryOp": {
                "left": {
                  "CompoundIdentifier": [
                    {
                      "value": "t1",
                      "quote_style": null
                    },
                    {
                      "value": "code",
                      "quote_style": null
                    }
                  ]
                },
                "op": "Eq",
                "right": {
                  "Value": {
                    "SingleQuotedString": "x0001"
                  }
                }
              }
            },
            "group_by": [],
            "cluster_by": [],
            "distribute_by": [],
            "sort_by": [],
            "having": null,
            "named_window": [],
            "qualify": null
          }
        },
        "order_by": [],
        "limit": null,
        "offset": null,
        "fetch": null,
        "locks": []
      },
      "without_rowid": false,
      "like": null,
      "clone": null,
      "engine": null,
      "default_charset": null,
      "collation": null,
      "on_commit": null,
      "on_cluster": null,
      "order_by": null
    }
  }
]

参考文献

https://qiita.com/Nakau/items/272bfd00b7a83d162e3a
https://www.setouchino.cloud/blogs/19
https://www.wakuwakubank.com/posts/676-linux-jq/
https://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af

関連記事

CodeBuild で パラメータストア / Secrets Manager を使う
https://dk521123.hatenablog.com/entry/2020/02/18/230358
機密データの管理 ~ Secrets Manager 編 ~
https://dk521123.hatenablog.com/entry/2020/03/12/220717
AWS CLI ~ --query / JMESPath ~
https://dk521123.hatenablog.com/entry/2024/01/07/000000