全力で怠けたい

怠けるために全力を尽くしたいブログ。

JMESPath を CLI で試せる jp コマンドが便利。

JMESPath を CLI で試せる jp コマンドが便利なのでメモしておく。

JMESPath と jp コマンドってなに?

JMESPathJSON のクエリ言語の1つで JSON からデータを抽出したり集計するのに使う。

JMESPath は AWS CLI--query オプションが JMESPath での指定になるので知っておいて損はないとは思う。 自分は普段は AWS CLI の出力は jq コマンドでパースとフィルタリングすることがほとんどなのだけど、たまに jq コマンドがインストールしてなかったりインストールできない環境で AWS CLI の出力をパースしたりフィルタリングするときにすごく困ってしまう。

そこで JMESPath を CLI でいろいろ試せるコマンドを探したら jp コマンドというのがあるみたいで、使ってみたらかなり便利だったのでメモしておく。

jp コマンドのバージョン。

$ jp --version
jp version 0.1.3

jp コマンドのインストール

Macbrew で jp コマンドをインストールできる。

$ brew tap jmespath/jmespath
$ brew install jmespath/jmespath/jp

jp コマンドの READMEbrew install jp でインストールするように記載してるけど brew install jphttps://github.com/sgreben/jp のほうをインストールしてしまうので、JMESPath を扱う jp コマンドのほうは brew install jmespath/jmespath/jp でインストールする必要がある。

README を修正する PR を作ろうとしたらすでに同じ目的の PR があったのだけど2年前の PR がずっと放置されているみたいで、全体的にあまりメンテナンスしていないっぽい。 メンテナンスしてないコマンドを使っていくのは少し不安があるけど jp コマンドのリポジトリをフォークしてるリポジトリは結構あるみたいなので、そのへんはまた探してみるのがよさそう。

github.com

jp コマンドの使い方

基本的な使い方

jp コマンドは jp <オプション> <JMESPath の式> みたいにして実行する。

jp コマンドはこんな感じで標準入力から JSON を読みこんで処理する。

$ echo '[0, 1, 2, 3, 5]' | jp @
[
  0,
  1,
  2,
  3,
  5
]

出力をダブルクォートで囲まない

jp コマンドの最終的な出力が文字列のときはダブルクォート (") で囲むけど --unquoted オプションか -u オプションを指定するとダブルクォート (") で囲まなくなる。jp コマンドの出力をパイプでほかのコマンドに接続するときに使うやつ。

$ echo '{"name": "bob"}' | jp name
"bob"

$ echo '{"name": "bob"}' | jp -u name
bob

JP_UNQUOTED 環境変数を設定しておくことでも出力をダブルクォートで囲まなくなる。

$ export JP_UNQUOTED=true
$ echo '{"name": "bob"}' | jp name
bob

JSON をファイルから読み込む

--filename オプションか -f オプションで JSON をファイルから読み込む。

$ cat data.json
[0, 1, 2, 3, 5]

$ jp -f data.json @
[
  0,
  1,
  2,
  3,
  5
]

JMESPath の式をファイルから読み込む

--expr-file オプションか -e オプションで JMESPath の式をファイルから読み込む。

$ cat data.json
[0, 1, 2, 3, 5]

$ cat expr.txt
[::2]

$ cat data.json | jp -e expr.txt
[
  0,
  2,
  5
]

JMESPath のチュートリアルをやってみる

jp コマンドの手習いとして JMESPath の チュートリアル をやってみる。

  • Basic Expressions
  • Slicing
  • Projections
    • List Projections
    • Slice Projections
    • Object Projections
    • Flatten Projections
    • Filter Projections

Basic Expressions

$ echo '{"a": "foo", "b": "bar", "c": "baz"}' | jp a
"foo"

$ echo '{"a": {"b": {"c": {"d": "value"}}}}' | jp a.b.c.d
"value"

$ echo '["a", "b", "c", "d", "e", "f"]' | jp [1]
"b"

$ cat data.json
{"a": {
  "b": {
    "c": [
      {"d": [0, [1, 2]]},
      {"d": [3, 4]}
    ]
  }
}}

$ cat data.json | jp  a.b.c[0].d[1][0]
1

Slicing

$ echo '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' | jp [0:5]
[
  0,
  1,
  2,
  3,
  4
]

$ echo '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' | jp [5:10]
[
  5,
  6,
  7,
  8,
  9
]

$ echo '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' | jp [:5]
[
  0,
  1,
  2,
  3,
  4
]

$ echo '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' | jp [::2]
[
  0,
  2,
  4,
  6,
  8
]

$ echo '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' | jp [::-1]
[
  9,
  8,
  7,
  6,
  5,
  4,
  3,
  2,
  1,
  0
]

Projections

List and Slice Projections

$ cat data.json
{
  "people": [
    {"first": "James", "last": "d"},
    {"first": "Jacob", "last": "e"},
    {"first": "Jayden", "last": "f"},
    {"missing": "different"}
  ],
  "foo": {"bar": "baz"}
}

$ cat data.json | jp 'people[*].first'
[
  "James",
  "Jacob",
  "Jayden"
]

$ cat data.json | jp 'people[:2].first'
[
  "James",
  "Jacob"
]

Object Projections

$ cat data.json
{
  "ops": {
    "functionA": {"numArgs": 2},
    "functionB": {"numArgs": 3},
    "functionC": {"variadic": true}
  }
}

$ cat data.json | jp 'ops.*.numArgs'
[
  2,
  3
]

Flatten Projections

$ cat data.json
{
  "reservations": [
    {
      "instances": [
        {"state": "running"},
        {"state": "stopped"}
      ]
    },
    {
      "instances": [
        {"state": "terminated"},
        {"state": "running"}
      ]
    }
  ]
}

$ cat data.json | jp reservations[*].instances[*].state
[
  [
    "running",
    "stopped"
  ],
  [
    "terminated",
    "running"
  ]
]

Filter Projections

$ cat data.json
{
  "machines": [
    {"name": "a", "state": "running"},
    {"name": "b", "state": "stopped"},
    {"name": "b", "state": "running"}
  ]
}

$ cat data.json | jp "machines[?state=='running'].name"
[
  "a",
  "b"
]

Pipe Expressions

$ cat data.json
{
  "people": [
    {"first": "James", "last": "d"},
    {"first": "Jacob", "last": "e"},
    {"first": "Jayden", "last": "f"},
    {"missing": "different"}
  ],
  "foo": {"bar": "baz"}
}

$ cat data.json | jp 'people[*].first | [0]'
"James"

MultiSelect

$ cat data.json
{
  "people": [
    {
      "name": "a",
      "state": {"name": "up"}
    },
    {
      "name": "b",
      "state": {"name": "down"}
    },
    {
      "name": "c",
      "state": {"name": "up"}
    }
  ]
}

$ cat data.json | jp 'people[].[name, state.name]'
[
  [
    "a",
    "up"
  ],
  [
    "b",
    "down"
  ],
  [
    "c",
    "up"
  ]
]

$ cat data.json | jp 'people[].{Name: name, State: state.name}'
[
  {
    "Name": "a",
    "State": "up"
  },
  {
    "Name": "b",
    "State": "down"
  },
  {
    "Name": "c",
    "State": "up"
  }
]

Functions

$ cat data.json
{
  "people": [
    {
      "name": "b",
      "age": 30,
      "state": {"name": "up"}
    },
    {
      "name": "a",
      "age": 50,
      "state": {"name": "down"}
    },
    {
      "name": "c",
      "age": 40,
      "state": {"name": "up"}
    }
  ]
}

$ cat data.json | jp 'length(people)'
3

$ cat data.json | jp 'max_by(people, &age).name'
"a"

$ cat data.json | jp "myarray[?contains(@, 'foo') == \`true\`]"
[
  "foo",
  "foobar",
  "barfoo",
  "barfoobaz"
]

参考サイト