〜2014年12月上旬〜
常にNexus7と同期させるようにしたためか、
fitbitのバッテリーの残量が少なくなるとメールを送ってくる。
これはかなり助かる。
同期してるとデバイスで見る機会が減るので、
気づかずに完全にOFFになってるときがあるんだよねえ。
jqって何でもありだな。
% echo $'foo:1\tbar:2\nfoo:3\tbar:4'
foo:1 bar:2
foo:3 bar:4
% echo $'foo:1\tbar:2\nfoo:3\tbar:4' | jq -s -R 'split("\n")|map(split("\t")|map(split(":")|{"key":.[0],"value":.[1:]|join(":")})|from_entries)'
[
{
"foo": "1",
"bar": "2"
},
{
"foo": "3",
"bar": "4"
}
]
valueに":"が含まれるときにsplitは最初の1個目でやめることができないので、 しかたなく2個目以降をjoinしている。それが
"value":.[1:]|join(":")
の部分。
サンプルログを拝借。
% curl -sL https://gist.github.com/masaru-b-cl/4743637/raw/2d89bf75af69f374ed19658d96d75e1c796535dd/access.ltsv | \
jq -s -R 'split("\n")|map(split("\t")|map(split(":")|{"key":.[0],"value":.[1:]|join(":")})|from_entries)'
[
{
"time": "[28/Feb/2013:12:00:00 +0900]",
"host": "192.168.0.1",
"req": "GET /list HTTP/1.1",
"status": "200",
"size": "5316",
"referer": "-",
"ua": "Mozilla/5.0",
"taken": "9789",
"isrobot": "1",
"dos": "-",
"harddos": "-",
"cache": "-"
},
{
"time": "[28/Feb/2013:12:00:00 +0900]",
"host": "172.16.0.12",
"req": "GET /list HTTP/1.1",
"status": "200",
"size": "5316",
"referer": "-",
"ua": "Mozilla/5.0",
"taken": "9789",
"isrobot": "1",
"dos": "-",
"harddos": "-",
"cache": "-"
}
]
実はjqには@csvなる変換機構が備わっている。 昨日の結果からvalueだけからなるCSVを作ってみる。
% cat access.json
[
{
"time": "[28/Feb/2013:12:00:00 +0900]",
"host": "192.168.0.1",
"req": "GET /list HTTP/1.1",
"status": "200",
"size": "5316",
"referer": "-",
"ua": "Mozilla/5.0",
"taken": "9789",
"isrobot": "1",
"dos": "-",
"harddos": "-",
"cache": "-"
},
{
"time": "[28/Feb/2013:12:00:00 +0900]",
"host": "172.16.0.12",
"req": "GET /list HTTP/1.1",
"status": "200",
"size": "5316",
"referer": "-",
"ua": "Mozilla/5.0",
"taken": "9789",
"isrobot": "1",
"dos": "-",
"harddos": "-",
"cache": "-"
}
]
% cat access.json | jq -r '.[]|map(.)'
[
"[28/Feb/2013:12:00:00 +0900]",
"192.168.0.1",
"GET /list HTTP/1.1",
"200",
"5316",
"-",
"Mozilla/5.0",
"9789",
"1",
"-",
"-",
"-"
]
[
"[28/Feb/2013:12:00:00 +0900]",
"172.16.0.12",
"GET /list HTTP/1.1",
"200",
"5316",
"-",
"Mozilla/5.0",
"9789",
"1",
"-",
"-",
"-"
]
% cat access.json | jq -r '.[]|map(.)|@csv'
"[28/Feb/2013:12:00:00 +0900]","192.168.0.1","GET /list HTTP/1.1","200","5316","-","Mozilla/5.0","9789","1","-","-","-"
"[28/Feb/2013:12:00:00 +0900]","172.16.0.12","GET /list HTTP/1.1","200","5316","-","Mozilla/5.0","9789","1","-","-","-"
""で囲まなくていいなら、単にjoinでok。
% cat access.json | jq -r '.[]|map(.)|join(",")'
[28/Feb/2013:12:00:00 +0900],192.168.0.1,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
[28/Feb/2013:12:00:00 +0900],172.16.0.12,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
この場合はmap(.)がなくてもいい。
% cat access.json | jq -r '.[]|join(",")'
[28/Feb/2013:12:00:00 +0900],192.168.0.1,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
[28/Feb/2013:12:00:00 +0900],172.16.0.12,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
このあたりの配列の扱いは注意。
昨日のLTSVからJSONと今日のJSONからCSVを連続でやれば、LTSVからCSVへ変換も一気にできる。
% cat access.ltsv
time:[28/Feb/2013:12:00:00 +0900] host:192.168.0.1 req:GET /list HTTP/1.1 status:200 size:5316 referer:- ua:Mozilla/5.0 taken:9789 isrobot:1 dos:- harddos:- cache:-
time:[28/Feb/2013:12:00:00 +0900] host:172.16.0.12 req:GET /list HTTP/1.1 status:200 size:5316 referer:- ua:Mozilla/5.0 taken:9789 isrobot:1 dos:- harddos:- cache:-
% cat access.ltsv |\
jq -s -R -r 'split("\n")|map(split("\t")|map(split(":")|{"key":.[0],"value":.[1:]|join(":")})|from_entries)[]|join(",")'
[28/Feb/2013:12:00:00 +0900],192.168.0.1,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
[28/Feb/2013:12:00:00 +0900],172.16.0.12,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
実は単に[]|join(",")が追加されただけ。
途中のkeyは無駄なので、それを省く。
% cat access.ltsv | jq -s -R -r 'split("\n")|map(split("\t")|map(split(":")[1:]|join(":")))[]|join(",")'
[28/Feb/2013:12:00:00 +0900],192.168.0.1,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
[28/Feb/2013:12:00:00 +0900],172.16.0.12,GET /list HTTP/1.1,200,5316,-,Mozilla/5.0,9789,1,-,-,-
こうなるとJSONどこ行ったんだという感じだが、世の中そんなもんである。
うささんからツッコミがあった。
jqの最新バージョンにはtestがあるので、
変換できるものをチェックしてtonumberすれば可能。
数値じゃないものをtonumberするとエラーで終了してしまう。
% jq -n '"abc" | tonumber' jq: error: Invalid numeric literal (while parsing 'abc')
というわけで正規表現で数値かどうか確認しないといけない。
% cat access.ltsv | jq -s -R -r 'split("\n")|map(split("\t")|map(split(":")|{"key":.[0],"value":(.[1:]|join(":")|if test("^[\\d.]+$") then tonumber else . end)})|from_entries)'
[
{
"time": "[28/Feb/2013:12:00:00 +0900]",
"host": "192.168.0.1",
"req": "GET /list HTTP/1.1",
"status": 200,
"size": 5316,
"referer": "-",
"ua": "Mozilla/5.0",
"taken": 9789,
"isrobot": 1,
"dos": "-",
"harddos": "-",
"cache": "-"
},
{
"time": "[28/Feb/2013:12:00:00 +0900]",
"host": "172.16.0.12",
"req": "GET /list HTTP/1.1",
"status": 200,
"size": 5316,
"referer": "-",
"ua": "Mozilla/5.0",
"taken": 9789,
"isrobot": 1,
"dos": "-",
"harddos": "-",
"cache": "-"
}
]
これちょっとわかりにくいが、
{"key":.[0], "value":.[1:] | join(":")}
が
{"key":.[0], "value":(.[1:] | join(":") | if test("^\\d+$") then tonumber else . end)}
になっている。外側もまとめて()で囲まないとエラーになるがどこを囲めばいいんだか悩んだ。
実は最新だとRubyのrescue的なものが存在するのでこれを使う手もある。
{"key":.[0], "value":(.[1:] | join(":") | tonumber? // .)}
?をつけるとエラーが起きても無視しろという意味になり、その値はnullとかempty扱いになる。
//はPerl6と同じ意味でRubyの||と同じ。
つまりエラーが起きたら . なので何も変わらないことになりif文と同じ結果になる。
split("\n")|map(split(" "))|group_by(.[0])|.[]|min_by(.[2]|tonumber)|join(" ")
のように"2014-11-01 21:10 14.5"を空白でsplitしてから処理してたけど、
そうすると最後にjoinしてまた元の形式に戻すというちょっとまどろっこしいことになっていた。
元の形式がほぼ固定長で最後の気温だけが可変長なのでうまいこと最初の文字列のまま処理できる。
% jq -s -r -R 'split("\n")|group_by(.[:11])[]|min_by(.[17:]|tonumber)' saitamashi-201412.log
2014-12-01 23:30 10.5
2014-12-02 22:40 4.2
2014-12-03 05:50 -0.6
2014-12-04 05:50 1.2
2014-12-01で10文字なので.[:11]、.[]はgroup_byと統合、気温は17番目から、splitしてないのでjoinも不要。
Rubyでやってみると非常によく似ていることがわかる。
% ruby -e 'puts $<.group_by{|x|x[0,10]}.map{|x|x[1].min_by{|y|y[17..-2].to_f}}' saitamashi-201412.log
2014-12-01 23:30 10.5
2014-12-02 22:40 4.2
2014-12-03 05:50 -0.6
2014-12-04 05:50 1.2
oauth2で認証は面倒なので、ブラウザで認証したtokenをそのまま使う。
chromeなら要素を検証でNetworkを選び適当になにかクリックしてAPIを発行させる。
その中から/reader/apiで始まるリクエストを選び右クリックしてCopy as cURLを選択。
この内容から-H 'Token: トークン'を抜き出す。あとはこれを指定すればok。
たとえばユーザー情報なら
% curl -s 'https://reader.aol.com/reader/user/info' -H 'Token: トークン' | jq -r .displayName eban
という感じでいける。
jq Manualを見るとsplit2で正規表現が使えそうな雰囲気だが、
最新版でも存在しない。
いったいどうなってるのか。
いろいろ試してみると第2引数を指定すると正規表現扱いになるようだ。
2番目の引数はmatchと同じmodifiersが使える。
% jq -cn '"foo bar baz"|split(" +")'
["foo bar baz"]
% jq -cn '"foo bar baz"|split(" +";"")'
["foo","bar","baz"]
しかし、なんかあやしい。
% jq -cn '"hoge"|split("";"")'
["","h","o","g","e"]
最初の""は何?
そうそう。それとは別にsplitsというのもある。
% jq -cn '"hoge"|splits("")'
""
"h"
"o"
"g"
"e"
結果は配列ではないのだよねえ。なんというかまだまだ安定しないようだ。
ideoneにはjqがないしさらしても問題ないかな。
% jq -n -r '"A"*25|explode|range(0;25)as$r|.[$r]+=$r+1|implode' BAAAAAAAAAAAAAAAAAAAAAAAA ACAAAAAAAAAAAAAAAAAAAAAAA AADAAAAAAAAAAAAAAAAAAAAAA AAAEAAAAAAAAAAAAAAAAAAAAA AAAAFAAAAAAAAAAAAAAAAAAAA AAAAAGAAAAAAAAAAAAAAAAAAA AAAAAAHAAAAAAAAAAAAAAAAAA AAAAAAAIAAAAAAAAAAAAAAAAA AAAAAAAAJAAAAAAAAAAAAAAAA AAAAAAAAAKAAAAAAAAAAAAAAA AAAAAAAAAALAAAAAAAAAAAAAA AAAAAAAAAAAMAAAAAAAAAAAAA AAAAAAAAAAAANAAAAAAAAAAAA AAAAAAAAAAAAAOAAAAAAAAAAA AAAAAAAAAAAAAAPAAAAAAAAAA AAAAAAAAAAAAAAAQAAAAAAAAA AAAAAAAAAAAAAAAARAAAAAAAA AAAAAAAAAAAAAAAAASAAAAAAA AAAAAAAAAAAAAAAAAATAAAAAA AAAAAAAAAAAAAAAAAAAUAAAAA AAAAAAAAAAAAAAAAAAAAVAAAA AAAAAAAAAAAAAAAAAAAAAWAAA AAAAAAAAAAAAAAAAAAAAAAXAA AAAAAAAAAAAAAAAAAAAAAAAYA AAAAAAAAAAAAAAAAAAAAAAAAZ
defで関数を定義できるが、一々毎回書いてらんないよねえ。
~/.jqに書いとけば読んでくれる。
たとえばtonumberは長いから短くしようと思ったらこんな感じで。
% echo 'def tn: tonumber;' > ~/.jq % jq -n '"1234" | tn' 1234
それにしてもどこにも書いてないな。
結構短く書けて意外。20未満の素数。
% jq -n 'range(2;20)|select([.%range(2;.)!=0]|all)' 2 3 5 7 11 13 17 19
エラトステネスの篩。そのままRubyで書くとこんな感じ。
% ruby -e 'puts (2...20).select{|x|(2...x).all?{|y|x%y!=0}}'
2
3
5
7
11
13
17
19