AWS CloudShellで出力される"jq: error string and number cannot be added"に対処するには


# 概要
この記事は、AWS CloudShellでjqのjoinを使用する際に発生するエラーの原因と解決方法を説明しています。

# AWS CloudShellでのjqエラー
AWS CloudShellで、jqを使用する際に発生するエラーについて説明します。

数値が含まれる配列を`join`しようとすると、`jq: error string and number cannot be added`が出力されます。

## 期待する結果

```bash
$ echo '["example",0]' | jq 'join(".")'
"example.0"
```

## 実際の結果
```bash
$ echo '["example",0]' | jq 'join(".")'
jq: error (at <stdin>:1): string (".") and number (0) cannot be added
```

# 原因

このエラーの原因について説明します。

CloudShellにプリインストールされているjqのバージョンは、1.5です。[^1]

[^1]: 記事投稿時点のバージョンです。将来バージョンアップされる可能性があります。

```bash
$ jq --version
jq-1.5
```

1.5のjoinにはバグがあり、number型の場合に文字列化されないため、当該エラーが出力されます。

https://github.com/stedolan/jq/blob/jq-1.5/builtin.c#L1422

```c
  "def join($x): reduce .[] as $i (null; (.//\"\") + (if . == null then $i else $x + $i end))//\"\";",
```

:::note info
このバグは1.6でFixされています。joinのソースを確認すると、number型を`tostring`で文字列化していることがわかります。
:::

https://github.com/stedolan/jq/blob/master/src/builtin.jq#L66-L69

```bash
def join($x): reduce .[] as $i (null;
            (if .==null then "" else .+$x end) +
            ($i | if type=="boolean" or type=="number" then tostring else .//"" end)
        ) // "";
```

issueが上がっていました。

https://github.com/stedolan/jq/issues/1595

# 解決手段１（jq1.5で動作するように修正する）
このエラーを解決する手段について説明します。

まず、jq1.5で動作するように修正する方法です。

joinに渡す前に数値を文字列化すれば、1.5でも期待通りに動作します。もちろん1.6でも問題なく動作します。

```bash
$ echo '["example",0]' | jq 'map(.|tostring) | join(".")'
"example.0"
```

# 解決手段２（jq1.6をインストールする）

次は、jq1.6をCloudShellにインストールする方法です。

ただし、CloudShellで用意されているyumでは、プリインストールされているjqをアップグレードできない[^2]ので、適当なディレクトリにバイナリをダウンロードするようにします。

[^2]:サポートに確認済みです。

:::note warn
ディレクトリの永続性について
- `/usr/bin`にバイナリファイルを配置すると、CloudShell再起動時にファイルが削除されるため、ご注意ください
- ホームディレクトリに配置した場合はデータが永続化されますが、120日を超えて当該CloudShell環境の利用がないと、削除されますのでご注意ください
:::

https://docs.aws.amazon.com/ja_jp/cloudshell/latest/userguide/limits.html#persistent-storage-limitations

## 実際の例
具体的な例を示し、解決方法を説明します。

ホームディレクトリにインストール先のディレクトリを作成し、バイナリをダウンロードします。ディレクトリは任意ですが、ここでは`jq1.6`とします。

```bash
cd ~
mkdir jq1.6
cd jq1.6
wget -O jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
chmod +x ./jq 
```

先述のとおり、1.6でこのバグはFixされているので、期待通りに動作します。

```bash
$ ~/jq1.6/jq --version
jq-1.6
$ echo '["example",0]' | ~/jq1.6/jq 'join(".")'
"example.0"
```

エイリアスを設定すると、便利です。

```bash
$ echo "alias jq=~/jq1.6/jq" >> ~/.bashrc
# エイリアスをログイン中のシェルに反映
$ . ~/.bashrc
$ echo '["example",0]' | jq 'join(".")'
"example.0"
```

## シェルスクリプトとして実行するには

:::note warn
シェルスクリプトとして実行する場合は、ログイン中のシェル環境が引き継がれないため、エイリアスが未定義となり、jq1.5が実行されてしまうことに注意する必要があります。
:::

```bash:join.sh
#!/bin/bash
echo '["example",0]' | jq 'join(".")'
```

```bash
$ bash ./join.sh 
jq: error (at <stdin>:1): string (".") and number (0) cannot be added
```

この場合は、`-i`オプションで対話的動作を指定すると、`~/.bashrc`が読み込まれてスクリプトが実行されるため、期待通りの動作になります。

```bash
$ bash -i ./join.sh 
"example.0"
```

# 結論
CloudShellで、jqを使用する際に発生するエラーについての対処法をまとめます。

２つの解決手段を説明しました。

解決手段１は、CloudShellの環境を変更する必要がなく、また、1.5と1.6どちらでも動作するため可搬性が高いことがメリットです。[^3]

[^3]:開発用PCなど自由にインストールできる環境であれば、最新バージョンである1.6が入っていることが多いと思います。

一方で、解決手段２は、1.6で動作させるので、joinエラー以外の挙動にも対応できることに優位性があります。

https://shogo82148.github.io/blog/2022/04/25/2022-04-25-jq-program/

状況に応じて、使い分けてください。

# 参考
https://stackoverflow.com/questions/46911763/jq-error-number-and-string-cannot-be-added