シェル芸
jq

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

More than 1 year has passed since last update.

はじめに

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

jqのインストール方法については、Qiitaインターネット上のページ を参考にして下さい。

第22回シェル芸勉強会 大阪サテライトLTで発表した内容 に加筆・修正を行ったものとなっております。

定番のヘルプ表示

$ 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.

カラー表示にする起動オプション

$ aws ec2 describe-instances | jq -C '.' | less -R
  • シェルのプロファイルで alias jqc='jq -C' とか export LESS='-R' とかしておくと良いかもしれないです。
  • サンプルデータ生成に AWS CLI のコマンドを使用するので、AWS CLIをセットアップするか、ダミーデータ aws-ec2-describe-instances.json をダウンロードして cat するかして下さい

JSONの抽出

$ 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の再構築

$ 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 }' #上記の短縮形

結果の確認方法

$ 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

$ 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 ]'

JSONの置換

$ 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内容となる
  • オブジェクトへのキー追加はオブジェクト同士の加算をするか、新規キーへの代入で行う
  • 他にもいろいろ略記方法が存在する

JSONの置換2

$ 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 と同意

結果の整形

$ 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    文字列の引用符を外す
  • @フォーマット名で出力形式の指定や、文字エスケープの指定を行う事ができる

あとは流れでシェル芸に

$ 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の読み込み

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

CSVの書き出し(ヘッダ先頭行付き)

$ 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変換処理をエイリアス登録しておくと、最後のコマンドの様に実行できて便利

引数の変数展開

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

式の変数化

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

文字列内の式展開

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

その他、使えそうな組み込み関数

関数名 機能
sort, sort_by 入力配列をソートする
unique, unique_by   入力配列の重複を削除する
group_by 入力配列をグループ化する
add 配列の各要素を数値の場合は合算、文字列は結合  
range 指定範囲の数値群を生成する

参考資料