Just another Ruby porter,

〜2014年12月上旬〜


<Older(,) | Newer(.)> | Recent(/)>> | RDF

2014-12-01 (Mon)

fitbitからのお知らせ

常にNexus7と同期させるようにしたためか、
fitbitのバッテリーの残量が少なくなるとメールを送ってくる。
これはかなり助かる。
同期してるとデバイスで見る機会が減るので、
気づかずに完全にOFFになってるときがあるんだよねえ。


2014-12-02 (Tue)

jqを使ってLTSVをJSONに変換する

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": "-"
  }
]

2014-12-03 (Wed)

jqを使ってJSONをCSVに変換する

実は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どこ行ったんだという感じだが、世の中そんなもんである。


2014-12-04 (Thu)

数値は数値へ

うささんからツッコミがあった。
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文と同じ結果になる。


2014-12-05 (Fri)

jqゴルフ

jqで日ごとの最低気温では

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

2014-12-06 (Sat)

AOL ReaderのAPIを使う

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

という感じでいける。


2014-12-07 (Sun)

jqのsplitで正規表現を使う

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"

結果は配列ではないのだよねえ。なんというかまだまだ安定しないようだ。


2014-12-08 (Mon)

ですまころしあむ

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

2014-12-09 (Tue)

jqで関数定義

defで関数を定義できるが、一々毎回書いてらんないよねえ。
~/.jqに書いとけば読んでくれる。
たとえばtonumberは長いから短くしようと思ったらこんな感じで。

% echo 'def tn: tonumber;' > ~/.jq
% jq -n '"1234" | tn'
1234

それにしてもどこにも書いてないな。


2014-12-10 (Wed)

jqで素数

結構短く書けて意外。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

<Older(,) | Newer(.)> | Recent(/)>> | RDF


WWW を検索 jarp.does.notwork.org を検索

わたなべひろふみ
Key fingerprint = C456 1350 085F A320 C6C8 8A36 0F15 9B2E EB12 3885
Valid HTML 4.01!