【Jqコマンド実用編】押さえておきたいデータ入出力のためのJqのコマンドオプションまとめ


2021/05/18
蛸壺の技術ブログ|押さえておきたいデータ入出力のためのJqのコマンドオプションまとめ

Jqコマンドのオプションはパース結果に影響を与える重要なものが多いのですが、久々に使うとすっかり使い方を忘れてしますうのは私だけでしょうか...

今回は防備録兼使い方ガイド的に押さえておきたいJqのオプションたちを取り上げてみます。


押さえておきたいI/O系コマンドオプション

Jqコマンドの基本はJSONストリームを入力し、定義されたなんらかのフィルタを通り、再構築された上でJSONストリームとして出力されるように働きます。

初学者がJqコマンドの入力ストリームのフォーマットの作法は慣れないうちは、色々と疑問に思うところも多いと思います。

特にJqの入力値の制御で重要な役割をしているオプションの内で、個人的に覚えておかなければならないものを以下に挙げます。

            
            --slurp / -s
--raw-input / -R
--null-input / -n
--compact-output / -c
--raw-output / -r
        

Jqマニュアルには結構サラッと説明してあります。

かなりドライな説明ですので、自分用の資料としても以降で各オプションの痒いところも併せて説明していきます。


利用方法

--slurp/-sオプション

このオプションでは、入力を単なるJSONオブジェクトとしては扱わず、入力ストリームを一旦配列に読み込ませておいて、その読み込みが完了した配列に対して一度だけフィルタを実行します。

言葉だけだと作用が分かりにくいので少し実例で説明します。

まずはダメな例として、後先考えず徐ろにslurpオプションを付けてみると、

            
            $ jq --slurp '.' << EOF
[
    {"name": "バミ田 ポプ雄","age": 43,"branch": "本社"},
    {"name": "ギャマ川 ノリュ実","age": 23,"branch": "福岡支店"},
    {"name": "ドル間 ハム吉","age": 33,"branch": "広島支店"},
]
EOF
#👇出力
[
  [
    {
      "name": "バミ田 ポプ雄",
      "age": 43,
      "branch": "本社"
    },
    {
      "name": "ギャマ川 ノリュ実",
      "age": 23,
      "branch": "福岡支店"
    },
    {
      "name": "ドル間 ハム吉",
      "age": 33,
      "branch": "広島支店"
    }
  ]
]
        
結果はJSON配列の外側に更に釘括弧で包まれた感じの出力を得ました。

slurpの特性を知らずにこのオプションを使うと、これは一体どういう理屈で外側の括弧が付いたのかと不思議に思われることもあるかも知れません。

slurpを使う場合でもっとも多いユースケースは、
個別のJSON要素の配列化です。

おそらく上の例でも、JSON配列化をやりたかったのに上手くいけてなかった際のJqの間違った使い方でよくあるパターンで失敗しています。

ではslurpオプションで上手くいくケースはどんな感じかといえば以下のようにします。

            
            $ jq --slurp '.' << EOF
{"name": "バミ田 ポプ雄","age": 43,"branch": "本社"}
{"name": "ギャマ川 ノリュ実","age": 23,"branch": "福岡支店"}
{"name": "ドル間 ハム吉","age": 33,"branch": "広島支店"}
EOF
#👇出力
[
  {
    "name": "バミ田 ポプ雄",
    "age": 43,
    "branch": "本社"
  },
  {
    "name": "ギャマ川 ノリュ実",
    "age": 23,
    "branch": "福岡支店"
  },
  {
    "name": "ドル間 ハム吉",
    "age": 33,
    "branch": "広島支店"
  }
]
        
今度はちゃんとJSON形式としてフィルタされ、期待するJSON配列を得たようです。

ここでのポイントは、slurpオプションが取る入力は
連続するJSONオブジェクトのストリームであり、入力として送らてきたJSONオブジェクトをjqが一つ一つ録り溜めて、最後に一つの配列として出力するときにつかうオプションです。

--raw-input/-Rオプション

文字通りこのオプションで取り込んだ入力値はJSONオブジェクトとして認識されず、改行文字区切りで各行ごとの文字列のストリームとして取り込まれます。

            
            $ jq --raw-input '.' << EOF
山田
佐藤
鈴木
EOF
#👇出力
"山田"
"佐藤"
"鈴木"
        
これだけだとほぼcatコマンドだし、一見役に立たなそうなオプションですが、Jsonフォーマット以外のデータからJSONオブジェクトを構築する場合に利用できます。

例えば以下は、単一の文字列 > 要素数1の配列化 > to_entriesの適用 >
.[]で配列を剥く(=to_entries[]で略記可能)の順にフィルタすることで、文字列からJSONオブジェクトのストリームに変換する例です。

            
            $ jq --raw-input '[.] | to_entries | .[]' << EOF
山田
佐藤
鈴木
EOF
#👇出力
{
  "key": 0,
  "value": "山田"
}
{
  "key": 0,
  "value": "佐藤"
}
{
  "key": 0,
  "value": "鈴木"
}
        
これでJSONオブジェクトのストリームとして出力できました。

更にここからこのJSONオブジェクトを配列化したい場合に、先程の--slurpオプションと組み合わせるとJSONオブジェクトの配列として出力できそう....?と考えてしまうかも知れませんが、--slurpと--raw-inputを併用した場合、改行文字をエスケープした入力全体が単一文字列として入力されてしまいます。

            
            $ jq --raw-input --slurp '.' << EOF
山田
佐藤
鈴木
EOF
#👇出力
"山田\n佐藤\n鈴木\n"
        
もしも--raw-inputから得られたJSONストリームをそのままJSON配列化したい場合には以下のようにプロセス置換などのテクニックを使えば良いでしょう。

            
            $ (jq --raw-input '[.] | to_entries[]' | jq --slurp '.')< <(
cat << EOF
山田
佐藤
鈴木
EOF
)
#👇出力
[
  {
    "key": 0,
    "value": "山田"
  },
  {
    "key": 0,
    "value": "佐藤"
  },
  {
    "key": 0,
    "value": "鈴木"
  }
]

#ちなみに以下のスクリプトでも同様
$ { jq --raw-input '[.] | to_entries[]' | jq --slurp '.'; }< <(
cat << EOF
山田
佐藤
鈴木
EOF
)
        
プロセス置換の丸括弧と中括弧の違いの説明はここでは行いませんので、気になったら検索されて調べてください。

ともあれ、--raw-inputオプションはJqが「私は入力フォーマットにとやかく言いませんので、データの成形はおたくでちゃんとやってね」的な機能です。

--compact-output/-cオプション

デフォルトではフィルター後の出力は人間にも見やすいように適当なインデント表示をしてくれますが、実際に中身を見ないJSONデータまで綺麗に表示する必要がない場合にはこの機能は冗長なものです。

--compact-outputオプションを用いると、JSONオブジェクトの表示をワンライナーに詰め込んだコンパクトな表示が可能となります。

            
            $ jq --compact-output '.' << EOF
[
    {"name": "バミ田 ポプ雄","age": 43,"branch": "本社"},
    {"name": "ギャマ川 ノリュ実","age": 23,"branch": "福岡支店"},
    {"name": "ドル間 ハム吉","age": 33,"branch": "広島支店"}
]
EOF
#👇出力
[{"name":"バミ田 ポプ雄","age":43,"branch":"本社"},{"name":"ギャマ川 ノリュ実","age":23,"branch":"福岡支店"},{"name":"ドル間 ハム吉","age":33,"branch":"広島支店"}]
        

--null-input/-nオプション

このオプションを指定した場合、どんな入力も受け付けない、すなわちnullとして扱われてしまします。

            
            $ jq --null-input '.' << EOF
[
    {"name": "バミ田 ポプ雄","age": 43,"branch": "本社"},
    {"name": "ギャマ川 ノリュ実","age": 23,"branch": "福岡支店"},
    {"name": "ドル間 ハム吉","age": 33,"branch": "広島支店"}
    {"name": "ホグ山 ミュン太","age": null,"branch": null}
]
EOF
#👇
null
        
なぜこのようなオプションが存在するのかというと、入力値をJqから生成する場合に便利だからです。

            
            $ jq --null-input '5 | . * 5 - 7 + (. - 3) * 2 | . - 17 | {"result": .} | [.]'
#👇出力
[
  {
    "result": 5
  }
]
        
上の例では、種として5という数値から、最終的にJSON配列を構築しています。

またJqコマンドにはforループ構文に当たるものはありませんが、この--null-inputとrange関数を基点にして、i以下のようなforループ処理の雛形のような感じに使うことも可能です。

            
            $ jq --null-input 'range(5) | {index: (. | tostring), odd_num: (. * 2 + 1)}'
#👇出力
{
  "index": "0",
  "odd_num": 1
}
{
  "index": "1",
  "odd_num": 3
}
{
  "index": "2",
  "odd_num": 5
}
{
  "index": "3",
  "odd_num": 7
}
{
  "index": "4",
  "odd_num": 9
}
        

--raw-output/-rオプション

最後に--raw-outputオプションのお話です。こちらは出力の制御に使うオプションです。

Jqコマンドの出力はデフォルトでパースしたJSONオプジェクトが結果として返される仕様になっています。

しばしばJqからJSONパースした出力を別のコマンドへ渡す場合に、JSONフォーマットのままでは都合が悪いケースがあります。

そこでJqでは出力フォーマットを制御できるように
@csvなどの出力フォーマットを指定できるフィルタ関数 - Format strings and escapingがいくつか用意されています。

例えば@csvフィルタで出力すると、

            
            $ jq '.[] | [.name, .age, .branch] | @csv' << EOF
[
    {"name": "バミ田 ポプ雄","age": 43,"branch": "本社"},
    {"name": "ギャマ川 ノリュ実","age": 23,"branch": "福岡支店"},
    {"name": "ドル間 ハム吉","age": 33,"branch": "広島支店"},
    {"name": "ホグ山 ミュン太","age": 44,"branch": "函館工場"}
]
EOF
#👇出力
"\"バミ田 ポプ雄\",43,\"本社\""
"\"ギャマ川 ノリュ実\",23,\"福岡支店\""
"\"ドル間 ハム吉\",33,\"広島支店\""
"\"ホグ山 ミュン太\",44,\"函館工場\""
        
となるのですが、素の@csvフィルタはダブルクォーテーション文字で囲った文字列ストリームを返す仕様です。このままでもテキストとして読めなくは無いのですが、もう少しこの引用符記号をどうにかしてほしいものです。

そこでこの--raw-outputオプションを使うと、システムの標準出力に則した出力を返してくれるようになります。

            
            $ jq --raw-output '.[] | [.name, .age, .branch] | @csv' << EOF
[
    {"name": "バミ田 ポプ雄","age": 43,"branch": "本社"},
    {"name": "ギャマ川 ノリュ実","age": 23,"branch": "福岡支店"},
    {"name": "ドル間 ハム吉","age": 33,"branch": "広島支店"},
    {"name": "ホグ山 ミュン太","age": 44,"branch": "函館工場"}
]
EOF
#👇出力
"バミ田 ポプ雄",43,"本社"
"ギャマ川 ノリュ実",23,"福岡支店"
"ドル間 ハム吉",33,"広島支店"
"ホグ山 ミュン太",44,"函館工場"
        
これでよりスッキリとした出力が得られました。


まとめ

今回はJqを使う上で欠かせない入力に関連した以下の5つのオプションを解説してみました。

            
            --slurp / -s
--raw-input / -R
--null-input / -n
--compact-output / -c
--raw-output / -r
        
これらのオプションの使いこなしはJqコマンドを極めるための本の序の口ですが、理解しておくのは重要なことだと思います。

参考サイト

jq 1.6 Manual

軽量JSONパーサー『jq』のドキュメント:『jq Manual』をざっくり日本語訳してみました

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。