JSONを加工するときに jq は必須の存在になりました。 jqハンドブック なんてものが発売されるくらいですからね。
そんな jq なんですが、@Gaku07jp が AWS CloudShell 上で 期待どおりに動かない、と困ってました。 なんでこんな挙動になるのかなーと気になったので、調査してみたメモです。
症状
問題となったのはこんな感じのシェルスクリプトです。
FOO=$(echo '{}' | jq)
普段開発で使用している macOS 上では FOO={}
と展開されるのですが、 CloudShell 上では jq のヘルプメッセージが表示されます (2022-04-25現在)。
# CloudShell 上
$ FOO=$(echo '{}' | jq)
jq - commandline JSON processor [version 1.5]
Usage: jq [options] <jq filter> [file...]
(...snip...)
原因
直接の原因は引数を省略してしまったことです。
jq の第一引数には jq の式が必要です。単に整形したい場合は jq .
とすればOKです。
FOO=$(echo '{}' | jq .)
macOS と CloudShell の違い
スクリプトが動かない直接の原因はわかったものの、同じ jq なのに、なぜこのような違いが生まれるのか気になりますよね? というわけでソースコードを追ってみました。
ヒントは問題のシェルスクリプトを実行したときに表示された、ヘルプメッセージにあります。 CloudShell の jq は version 1.5 と少し古いバージョンです。 一方、弊社では macOS の jq は brew 経由でインストールしているので、最新版の version 1.6 がインストールされています。
# macOS 上
$ jq
jq - commandline JSON processor [version 1.6]
Usage: jq [options] <jq filter> [file...]
jq [options] --args <jq filter> [strings...]
jq [options] --jsonargs <jq filter> [JSON_TEXTS...]
(...snip...)
なるほど、この間になにか修正が入ったんだな?と、差分を確認してみました。
挙動から推測するに「入出力が TTY か」で条件分岐しているんだろうなと当たりをつけ、差分をザーッと眺めてみると、ありました! usage を表示する部分が以下のように変更されていました。
@@ -457,18 +558,12 @@ int main(int argc, char* argv[]) {
else
jq_set_attr(jq, jv_string("VERSION_DIR"), jv_string_fmt("%.*s-master", (int)(strchr(JQ_VERSION, '-') - JQ_VERSION), JQ_VERSION));
-#if (!defined(WIN32) && defined(HAVE_ISATTY)) || defined(HAVE__ISATTY)
-
-#if defined(HAVE__ISATTY) && defined(isatty)
-#undef isatty
-#define isatty _isatty
-#endif
-
- if (!program && isatty(STDOUT_FILENO) && !isatty(STDIN_FILENO))
+#ifdef USE_ISATTY
+ if (!program && (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)))
program = ".";
#endif
- if (!program) usage(2);
+ if (!program) usage(2, 1);
if (options & FROM_FILE) {
char *program_origin = strdup(program);
git blame して この変更が入ったコミットを見てみると、 以下のプルリクエストで取り込まれたものでした。
- Assume . if either stdin/on isatty() (fix #1028) #1030
- Redirecting or piping output has different behaviour #1028
version 1.5 と version 1.6 の違いをまとめると、こんな感じになります。
echo '{}' | jq > foo
→ version 1.5 では usage が表示される。version 1.6 では、ファイルfoo
にJSONのパース結果が保存されるjq > foo
→ version 1.5 では usage が表示される。 version 1.6 では jq が入力待ち状態になり、ファイルfoo
にJSONのパース結果が保存されるecho '{}' | jq
→ version 1.5, 1.6 ともにJSONのパース結果が出力されるjq
→ version 1.5, 1.6 ともに usage が表示される
まとめ
macOS 上と CloudShell 上で jq の挙動が違うのは、 jq のバージョンの違いが原因でした。 jq version 1.5 と version 1.6 では、入出力が TTY の場合の挙動が少し変わっています。
さて、標準で jq version 1.5 がインストールされる主要な Linux ディストリビューションとして、 Ubuntu 18.04 LTS と RHEL7 があります。 Ubuntu 18.04 LTSのサポート期限は2023年4月、RHEL7のサポート期限が2024年6月30日なので、 あと 1〜2年は jq version 1.5 がインストールされた環境が残るわけですね(その後なくなるとは言っていない)。 ちょっとした違いですが注意しましょう。
個人的には jq .
と必ず引数を渡すのが良いと思います。
「この場合は省略できるんだけっけ・・・?」と考える必要がないので、楽ちんです。