1. ホーム
  2. json

[解決済み] UnixツールでJSONをパースする

2022-03-18 13:39:12

質問

curlリクエストから返されたJSONをパースしようとしているのですが、以下のような感じです。

curl 'http://twitter.com/users/username.json' |
    sed -e 's/[{}]/''/g' | 
    awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'

上記では、例えばJSONをフィールドに分割しています。

% ...
"geo_enabled":false
"friends_count":245
"profile_text_color":"000000"
"status":"in_reply_to_screen_name":null
"source":"web"
"truncated":false
"text":"My status"
"favorited":false
% ...

特定のフィールドを印刷するには、どうすればよいのでしょうか? -v k=text )?

解決方法は?

コマンドラインからJSONを操作するために特別に設計されたツールが多数あり、Awkで操作するよりもずっと簡単で信頼性が高いでしょう。 jq :

curl -s 'https://api.github.com/users/lambda' | jq -r '.name'

また、Python のようなすでにシステムにインストールされているツールでこれを行うこともできます。 json モジュール そのため、適切なJSONパーサーの利点を生かしつつ、余分な依存関係を回避することができます。以下では、オリジナルのJSONがエンコードされているはずのUTF-8を使用することを想定しており、ほとんどの最新端末でも使用されているものです。

Python 3:

curl -s 'https://api.github.com/users/lambda' | \
    python3 -c "import sys, json; print(json.load(sys.stdin)['name'])"

Python 2です。

export PYTHONIOENCODING=utf8
curl -s 'https://api.github.com/users/lambda' | \
    python2 -c "import sys, json; print json.load(sys.stdin)['name']"

よくある質問

なぜピュアシェル・ソリューションではないのですか?

標準的な POSIX/Single Unix仕様のシェル は非常に限定された言語であり、シーケンス(リストや配列)や連想配列(他の言語ではハッシュテーブル、マップ、ディクテ、オブジェクトとも呼ばれる)を表現する機能を備えていないのです。このため、移植可能なシェルスクリプトでは、JSONをパースした結果を表現するのがやや厄介になります。以下のようなものがあります。 ややハッキング的な方法 しかし、その多くはキーや値に特定の特殊文字が含まれると壊れてしまいます。

Bash 4以降、zsh、そしてkshは配列と連想配列をサポートしていますが、これらのシェルは普遍的に利用できるわけではありません(macOSはGPLv2からGPLv3への変更によりBash 3での更新を停止し、多くのLinuxシステムにはzshが箱から出してインストールされていません)。Bash 4 か zsh のどちらかで動作するスクリプトを書くことは可能です。zsh は最近の macOS、Linux、BSD システムのほとんどで利用可能ですが、このようなポリグロットスクリプトのために動作する shebang 行を書くのは大変でしょう。

最後に、シェルで本格的なJSONパーサーを書くとなると、jqやPythonのような既存の依存関係を代わりに使った方が良いくらい、重要な依存関係になります。良い実装をするためには、一行で、あるいは小さな5行のスニペットで済むものではありません。

なぜ awk や sed、grep を使わないのですか?

これらのツールを使って、形がわかっていて、1行に1つのキーというようにフォーマットが決まっているJSONから、いくつかの簡単な抽出をすることは可能です。他の回答に、このための提案の例がいくつかあります。

しかし、これらのツールは行ベースまたはレコードベースのフォーマット用に設計されており、エスケープ文字の可能性がある区切り文字にマッチした再帰的なパースには向いていません。

そのため、awk/sed/grep を使ったこれらの迅速で汚い解決策は壊れやすく、空白を折りたたんだり、JSON オブジェクトに追加の入れ子レベルを追加したり、文字列内にエスケープした引用を入れるなど、入力形式の一部が変わると壊れてしまう可能性が高いです。すべてのJSON入力を壊さずに処理できるほど堅牢なソリューションは、かなり大規模で複雑なものになります。 jq やPythonを使うことができます。

私は以前、シェルスクリプトの入力解析がうまくいかず、大量の顧客データが削除されたことがあります。ですから、このように壊れやすいかもしれないクイック&ダーティな方法は決してお勧めしません。一回限りの処理であれば、他の回答も参考になりますが、やはりテスト済みの既存のJSONパーサーを使うことを強くお勧めします。

歴史的なメモ

この回答は、もともと jsawk と比べて、使い勝手が少し悪いです。 jq また、Pythonよりも一般的ではないスタンドアロンのJavaScriptインタプリタがインストールされている必要があるため、上記の回答が望ましいと思われます。

curl -s 'https://api.github.com/users/lambda' | jsawk -a 'return this.name'

この回答も、もともとは質問にあったTwitterのAPIを使っていたのですが、そのAPIが使えなくなり、例をコピーしてテストするのが難しくなったのと、新しいTwitter APIはAPIキーが必要なので、APIキーなしで簡単に使えるGitHub APIを使うことに切り替えました。 元の質問に対する最初の回答は、次のようになります。

curl 'http://twitter.com/users/username.json' | jq -r '.text'