joコマンドさようなら、俺はパイプが好きだから。Colonel)joコマンドさようなら、俺はパイプが好きだから。2016-09-15joというJSON生成コマンドがあるらしいがJSONを読み解くコマンドのjqの反対の位置づけとして、joというコマンドが存在する。jqと同様、UNIXの作法をわかっていないし、オマケにこちらは使い方に気を付けないとセキュリティー上の問題を誘発しかねず、つくづく呆れてしまう。こんなものを使って喜んでいる連中も嘆かわしい。そこでPOSIX原理主義に基づき(=どのUNIX系OSにもコピーしただけで使える形で)、makrj.shというJSONジェネレーターを作った。これがあれば、シェルスクリプトからJSONデータを簡単に生成できる。JSONという形式がUNIX哲学的にはあまり好きではないが、現在主流のWebAPIに話しかけるには必要だろう。joがなぜダメなのか(さっさと使いたいという人は、ここは読み飛ばして次節へどうぞ)なんでもかんでも引数からデータ与えるな!joの最もダメな仕様。それは、データを引数から与えさせることだ。key=valueなどとやる。a,b,cとやり、複雑なJSONを作りたければ次のようにして複数のjoコマンドを組み合わせるらしい。a,b,c)なんでもかんでも引数からデータを与えるんじゃねえ!UNIXというのは、データを標準入力から受け取り、それをどのように加工するかの指示をコマンドや引数でやるのに適した作りをしているのだ。引数領域はデータを渡す場所じゃない。ARG_MAXというパラメーターがあるのがそのいい証拠だ。引数領域がデータを渡すための場所であるなどと考えられていないからこそこのような制約が設計上存在するのだ。(だいたいjoコマンドではARG_MAXを超えるサイズのJSONが作れないじゃないか)それに、本来指示を与える領域にデータを流し込んだら、セキュリティーインシデントの温床になる。データ中に不正な指示を与えるコードが潜り込まされていたらどうなると思う?というわけで、基本的な発想そのものがUNIX哲学を無視していると言わざるを得ない。今回JSONデータを生成するために作ったコマンドmakrj.shの行数はご覧のとおり200行程度。コメントを除けば実質120行程度だ。制作に要した日数は丸一日。使ったものはPOSIXで保証されたコマンドとシェルスクリプトだけ。何が言いたいかというと、もっと自分で作るという発想を持とうぜ、ということだ。探したソフトウェアのver.upやサポート終了を恐れながら、メンテナンスに追われる自分だ。すべて自分で作れとは言わない。他人の作ったものを使うのはもちろんアリだ。自分れ作れないかと検討する習慣をつけること使うのにしても、自分で作ったかのように深い理解をしながら使うことヘンなもの(UNIX哲学をわかってないjq,joなど)に手を出さないことを心がけるべきだ。JSONを自作する(デモ)makrj.shでできることと、使い方を理解できるように1つ目のデモを示す。(2つ目は別記事でやる予定)デモは、1人1行で表形式にまとめられている会員情報をJSON構造に変換するというものだ。今、次のような内容のデータがあったとする。KO23半角スペース区切りになっているが最終的にこの形にすればよく、元々がCSV等でもよい。(sedやAWKで自力で変換してもよいし、parsrc.sh等を使えばこの形式に容易に変換できる)このデータの各列の意味は次のとおりである。友人会員一覧6:友人1会員ID7:友人2会員ID:1列目が会員IDでありこれが主キー、2〜5列目はその会員の基本情報で今のところ列数は4種類固定、6列目以降はその会員に友人会員がいればその会員のIDを列数可変で持つというものである。求められるJSONデータ構造の仕様さて、先ほどのデータを、とあるWebAPIに流し込むことになったとする。そのWebAPIはJSONでしか受け取ってくれれず、そのフォーマットは次のような構造を要求しているものとする。私なりに表現してみたが、階層構造、その階層における変数のキー名などはだいたいこんなんな感じだったとする。[]はその中身が連想配列ではなく配列で格納されていることを示すものとする。:確かに一見すると綺麗に整理されてるけどね......。インターフェースがこうなっていってブツクサ1言っても仕方がないので変換しよう。「氏名」の姓名の区切り文字が代っている友人の数を追加するように求められている。3.変換シェルスクリプトを書く。先ほど把握した元の表形式データ形式と欲しいJSONデータ形式に基づくと、次のようなコードになる。|makrj.sh最初のAWKでJSONPath-value形式という2列構成(つまりkey-value形式)のデータを生成し、それをmakrj.shコマンドに食わせるだけだ。途中にteeコマンドを挿んであるが、これはここでのチュートリアルのためにJSONPath-value形式の中間データがどういう内容であるかを見てもらう目的で入れたものであり、本番では必要ない。書いたらさっそく実行だ。実行したらJSON形式に変換された会員データがめでたく出てくるのだが、その前に中間データの中身を見てみよう。3$をルート階層とし、.を階層区切り文字として、キー名(連想配列名)を書き連ね、各データの位置が示された文字列が行頭にあるがこれがJSONPathである。連想配列ではなく配列にしたい場合はキー名の後に[n]で番号をつける。ここらへんの詳しい仕様はもちろんJSONPathの公式ページを見ればわかるが、ここまで説明すればだいたい感覚的につかめることだろう。JSONデータ化された会員情報データの内容さて、先ほどのプログラムを実行したら画面上には既にJSONが表示されているはずだ。人に読ませることは重要視しておらず適当に改行が挟まれているだけ2で、インデントなどは一切行っていない。そのため、そのままここに記しても見づらいので、適当に整形して示すことにする。実際に出力されたものを見て確認したいのであれば、JSONLintというサイトにコピー&ペーストするのがよいだろう。]}ちゃんと生成できてるでしょ?こういうデータを出力するプログラムを毎回一から書くのは大変だ。(特にカッコの開きや閉じの条件判定とかで)注意キー名に半角空白やピリオド、角カッコは使わないそんなJSONを設計してるやつは死ねと言いたい。技術的にできないこともないが、そんな偏屈なJSONに対応して処理速度を落としたくなどない。それは「90%の解を目指せ」といっているUNIX哲学にも反する。しても必要ならば、半角空白は\u0020、ピリオドは\u0020、角カッコは\u005bと\u005dでAPIに渡せばいい。JSONPath列と値列の間は半角空白1つ中間データ(JSONPath-value形式)において、JSONPathの列と値の列の区切りは必ず半角空白1つにすること。:後者(悪い例)の場合は、IDは“_________TR03”(_は半角空白の意味)と見なされる。makrj.shコマンドは、実数、true/false、nullなどのデータ型はある程度臨機応変に認識するが、前後に余分なスペースが含まれていたら容赦なく文字列型とみなすようにしている。よって、後者の例だとpaidもfalseではなく手前に半角空白が2つ入っている文字列“__false”(_は半角空白の意味)として扱われる。行の順番を守るmakrj.shコマンドは、JSONを生成するにあたってJSONPathの変化を見ながらやっている。本来隣り合って配置されるべきデータは中間データでも行としても隣り合っていなければならない。隣り合っていなかった場合は、無駄に個々のデータが別々のカッコで独立した、とてもヘンなデータが生成されるので注意。PART2に続くPART2では、既存のJSONを受け取り、parsrj.shコマンドでJSONPath-value形式に変換して、加工して、元に戻す話を書く予定。べつに行列形式でだっていいのにね。最近のシステムはこういうところに無駄なエネルギーを使い過ぎだと思う。改行を挿れておかないと、UNIXの処理バッファーが満杯になるまでなかなかデータが出てこないし、後続のコマンドの処理が重たくなる。