bashでJSONに変数を埋め込むための3つの方法

昨今のWEB APIは、情報のやりとりにJSONを用いることが多いかと思います。Linuxのサーバを運用していると、手軽に書けるシェルスクリプトを使っていろいろなAPIを叩く機会が多いのですが、JSONの扱いには頻繁に悩まされます。

その悩みの一つは、いかにしてJSONに変数を埋め込むかというところです。例えば

{ "foo" : "bar" }

のようなJSONがあったとします。ここで、fooのvalueであるbarの部分を、値を変数として受け取り、それを設定したいことになりました。JSONの文字列はダブルクォートで囲まれている必要があるため、コマンドラインやシェルスクリプトで利用する時にはシングルクォートで囲む必要があります。ですが、そうすると変数展開の機能が使えなくなってしまいます。

筆者も今までに何度か悩みました。この投稿では、これまでに使った3つの方法をご紹介します。

今回は、例として以下のJSONを作ってみようと思います。JSONのvalueの部分を変数を使って埋め込む想定です。

{
  "id": 1,
  "name": "myname",
  "favorites": [
    "baseball",
    "videogame"
  ]
}

なお、動作確認のため、OSはMacOSとAmazon Linuxを、シェルはbashを利用して動作確認しています。 また、JSONのパースにはjqを使っています。

1. 愚直にシングルクォート駆使

素直にやるなら、シングルクォートを駆使することで変数埋め込みをすることができます。 単純な例であれば

$ var=bar
$ echo '{"foo" : "'$var'"}' | jq
{
  "foo": "bar"
}

のように、変数$varの部分をシングルクォート外にすることで、変数を展開しています。ダブルクォートはシングルクォートの中に文字列として書くことで、生成されるvalueがJSONのStringになるようにしています。

では、今回のお題のJSONをこの方法で書いてみます。

=== input ===
id=1
name=myname
favorites='"baseball","videogame"'
echo '{
  "id": '${id}',
  "name": "'${name}'",
  "favorites": [
    '${favorites}'
  ] 
}' | jq
=== output ===
{
  "id": 1,
  "name": "myname",
  "favorites": [
    "baseball",
    "videogame"
  ]
}

どうでしょうか。JSON部分がシングルクォート地獄になっており、私はこれをメンテナンス出来る気がしません。。実際にブログ用にこれを書いているときにも何度かクオートの記述ミスをしました。

もうちょっと楽な方法はないでしょうか、ということで次です。

2. ヒアドキュメントを使う

bashのヒアドキュメント機能を利用すると、比較的素直に長いJSONを書くことができます。

bashのヒアドキュメントは、以下のような文法になります。また、ヒアドキュメントは文字列ではなく標準入力として扱われるので、echoではなくcatを利用する必要があります。

$ var=bar
$ cat << EOS | jq
{
  "foo": "${var}"
}
EOS

{
  "foo": "bar"
}

ヒアドキュメントを使って、お題のJSONを作ってみましょう。

=== input ===
id=1
name=myname
favorites='"baseball","videogame"'
cat << EOS | jq
{
  "id": ${id},
  "name": "${name}",
  "favorites": [
    ${favorites}
  ]
}
EOS
=== output ===
{
  "id": 1,
  "name": "myname",
  "favorites": [
    "baseball",
    "videogame"
  ]
}

1番の例よりも、スッキリしたJSONが書けたと思います。が、まだ配列の部分がちょっとダサいですね。大抵の例ではこの方法で良さそうですが、もう一つprintfを使った方法をご紹介します。

3. printfを使う

printfコマンドはCのprintfと同じような文字列フォーマットを行ってくれます。使い方もほとんど変わりません。詳しい使い方はman printf -S 1で。

$ var=bar
$ printf '{"foo":"%s"}' $var | jq
{
  "foo": "bar"
}

%sで記載したplaceholderに、続く引数で渡している$varの値が入りました。ヒアドキュメントよりも行数が短く済むので、ワンラインで済むような場合にはかなり見やすくなるのではないでしょうか。

それでは、お題のJSONを作ってみます。

=== input ===
id=1
name=myname
favorites='"baseball","videogame"'
printf '{
  "id": %d,
  "name": "%s",
  "favorites": [
    %s
  ]
}' $id $name $favorites  | jq
=== output ===
{
  "id": 1,
  "name": "myname",
  "favorites": [
    "baseball",
    "videogame"
  ]
}

どうでしょうか?ヒアドキュメントを使う方法よりも行数は少なくなりました。一方で、printfで使うplaceholderと変数が離れてしまったことにより、関連性がちょっとわかりづらくなってしまいました。これ以上JSONが複雑になると実用が大変になってしまう気がします。

個人的には2のヒアドキュメントか、3のprintfを使う方法がオススメです。bashでJSONを扱う時は複雑になりがちなので、状況に合わせてメンテナンスしやすい方を選びましょう!また、複雑なJSONを扱う必要があるときには、シェルにこだわらずにLLを用いて実装する判断をすることも大切だと思います。

まとめ

私自身、bash上でJSONの変数展開をする時には何度も悩み、ハマりました。個人的には2番のヒアドキュメントを使う方法を採用していたのですが、今日ふと3番の方法があることを思いつき、これまでに自分がたどってきた方法をまとめてみました。

この他にも、有効な方法はあると思います。自分はこうやっているよー、という方法があれば、ぜひコメントやSNS等で教えてください!