ioknifeというパッケージを作りはじめた

github.com

はじめに

ioknifeというパッケージを作りはじめた。日常生活におけるちょっとしただるさを改善するようなコマンドを用意したいという思いで。

基本的にはioknifeというコマンドの中に色々欲しくなったらサブコマンドを追加していくというような方針(いまはrest,grepo,tooの3つだけ)。

言語は?

最初はpythonで作るかgoで作るかrustで作るか迷ったのだけれど、結局pythonで作ることにした。 速さはあまり要らないような細々とした処理を想定しているということと、まだどのような処理を追加したいかがあやふやなので動的に色々渡せるような何かがありえるかもしれないかなーと思ったりしているので。

いつか便利なサブコマンドができて、そしてそのサブコマンドの速度が気になったときに、それだけをrustなりで作るのが良さそうかなとおもったりした。

ちょっとした裏テーマとして、標準ライブラリのみで書く、mypyである程度まじめに型チェックをしてみる、というようなことを考えてたりはしている(標準ライブラリは概ねtypeshedにあるので)。

install方法

はい。

$ pip install ioknife

今の所存在するサブコマンドたち

今の所存在するサブコマンドは以下の3つ。

  • rest
  • grepo
  • too

help messageは概ねいつものどおり。

$ ioknife -h
usage: ioknife [-h] [--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
               [--debug DEBUG]
               {rest,grepo,too} ...

positional arguments:
  {rest,grepo,too}

optional arguments:
  -h, --help            show this help message and exit
  --logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}
  --debug DEBUG

ioknife rest

restコマンドは関数のhead,tailのtailをイメージするとわかりやすいかもしれない。行をリストの要素になぞらえた形の。ただし、取り除かれるであろう先頭のN行は表示しないのではなくstderrの出力される。

どういう時に使うかと言うと、先頭にヘッダーが付加されるようなコマンドをgrepするような時に。典型的な例はpsコマンド。

通常のpsコマンドでgrepした場合にはヘッダー行が喪われてしまう。以下の様に。

$ ps aux | grep -i bash
podhmo       4435  0.0  0.0  10052  6336 pts/1    Ss+   6月09   0:06 /bin/bash
podhmo       4440  0.0  0.0  10328  6544 pts/2    Ss+   6月09   0:09 /bin/bash
podhmo       6431  0.0  0.0   9144  5288 pts/0    Ss    6月20   0:00 bash
podhmo       7832  0.0  0.0   9128  4932 pts/6    Ss    6月19   0:00 /bin/bash --noediting -i
podhmo       9100  0.0  0.0   7308  2300 pts/6    S+   00:49   0:00 grep --color=auto -i bash
podhmo      28622  0.0  0.0   9276  5408 pts/3    Ss+   6月19   0:00 /bin/bash
podhmo      28641  0.0  0.0   9408  5412 pts/4    Ss+   6月19   0:00 /bin/bash

本来は以下のようなヘッダー行が表示されるのだけれど隠れてしまう。

$ ps aux | head -n 1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

これをstderrに表示してあげると便利というようなコマンド(-n 1 はデフォルトなので省略しても大丈夫)。

そしてstderrはgrepの対象にはならないのでconsole上には表示される。便利。

$ ps aux | ioknife rest -n 1 | grep bash
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
podhmo       4435  0.0  0.0  10052  6336 pts/1    Ss+   6月09   0:06 /bin/bash
podhmo       4440  0.0  0.0  10328  6544 pts/2    Ss+   6月09   0:09 /bin/bash
podhmo       6431  0.0  0.0   9144  5288 pts/0    Ss    6月20   0:00 bash
podhmo       7832  0.0  0.0   9128  4932 pts/6    Ss    6月19   0:00 /bin/bash --noediting -i
podhmo       9185  0.0  0.0   7176  2424 pts/6    S+   00:51   0:00 grep --color=auto bash
podhmo      28622  0.0  0.0   9276  5408 pts/3    Ss+   6月19   0:00 /bin/bash
podhmo      28641  0.0  0.0   9408  5412 pts/4    Ss+   6月19   0:00 /bin/bash

ioknife grepo

grepoコマンドは grep -o <pattern> を模したコマンド。grep-o オプションはマッチした部分のみを抽出するオプションでほかのコマンドにパイプで渡すような時に結構重宝する機能。

一方で、その行自体の内容がわからなくなる。これも同様にstderrに出力されれば便利なのでは?というような形で作成したもの。

例えば、先程のpsの例をgrepで取り出してみる。

$ ps aux | grep -o -P "[^ ]+/bash"
/bin/bash
/bin/bash
/bin/bash
/bin/bash
/bin/bash

何がなんだかわからない。これをgrepoを使うと以下の様な表示になる。実は分かりづらいけれどstderrの表示は灰色っぽい感じの色で表示される。

$ ps aux | ioknife grepo "[^ ]+/bash"
matched: podhmo       4435  0.0  0.0  10052  6336 pts/1    Ss+   6月09   0:06 /bin/bash
/bin/bash
matched: podhmo       4440  0.0  0.0  10328  6544 pts/2    Ss+   6月09   0:09 /bin/bash
/bin/bash
matched: podhmo       7832  0.0  0.0   9128  4932 pts/6    Ss    6月19   0:00 /bin/bash --noediting -i
/bin/bash
matched: podhmo      28622  0.0  0.0   9276  5408 pts/3    Ss+   6月19   0:00 /bin/bash
/bin/bash
matched: podhmo      28641  0.0  0.0   9408  5412 pts/4    Ss+   6月19   0:00 /bin/bash
/bin/bash

ちなみにpythonでナイーブに実装されているのでそんなに早くない。そこそこ大きめのファイルなどに対して使いたい場合には、以下のようにgrepなどの後に使った方が良い(二重にパターンを書かなければいけないのがめんどくさかったりはする)。

$ ps aux | grep -P "[^ ]+/bash" | ioknife grepo "[^ ]+/bash"

実行例は以下の様な感じ。

asciicast

ioknife too

tooコマンドはtoo.jsにとても影響を受けている。

github.com

説明自体もtoo.jsの以下のものそのまま。

Combine multiple commands' stream, keep all foreground and kill all in one Ctrl+C

自作した理由は、日常的な環境ではnpmに依存したものをあまり使いたくなかったなーということとasyncioとsubprocessを使って遊んでみたかったので(CI経由でそう言えばcontextlib.asynccontextmanager()とasyncio.run()がpython3.7からだったということを思い出したりしていた)。

実行した例は以下の様な感じ。雰囲気で色が付く。

asciicast

--shell オプションを有効にするとシェルを経由して実行される(experimental)。--dump-context を付けるとどんな感じの雰囲気で実行されるか分かる。

$ ioknife too --dump-context --shell --cmd 'for i in $(seq 3); do echo "--" $i; sleep 0.4; done'  --cmd 'for i in $(seq 3); do echo "\*\*" $i; sleep 0.4; done'
['for', 'i', 'in', '$(seq', '3);', 'do', 'echo', '--', '$i;', 'sleep', '0.4;', 'done']
['for', 'i', 'in', '$(seq', '3);', 'do', 'echo', '\\*\\*', '$i;', 'sleep', '0.4;', 'done']

$ ioknife too --shell --cmd 'for i in $(seq 3); do echo "--" $i; sleep 0.4; done'  --cmd 'for i in $(seq 3); do echo "\*\*" $i; sleep 0.4; done'
[0] for      -- 1
[1] for      ** 1
[0] for      -- 2
[1] for      ** 2
[0] for      -- 3
[1] for      ** 3

ちなみに --cmd と入力するのが面倒だったのでstdinからも受け取れるようにした。ただし渡せるファイルのフォーマットは後で変更するかもしれない。

/tmp/x

for i in $(seq 10); do echo "--" $i; sleep 0.1; done
for i in $(seq 10); do echo "@@" $i; sleep 0.1; done

使った結果。

$ cat /tmp/x | ioknife too --shell --dump-context
['for', 'i', 'in', '$(seq', '10);', 'do', 'echo', '--', '$i;', 'sleep', '0.1;', 'done']
['for', 'i', 'in', '$(seq', '10);', 'do', 'echo', '@@', '$i;', 'sleep', '0.1;', 'done']

途中で止める。

$ cat /tmp/x | ioknife too --shell
$ cat /tmp/x | ioknife too --shell
[0] for      -- 1
[1] for      @@ 1
[0] for      -- 2
[1] for      @@ 2
[0] for      -- 3
[1] for      @@ 3
[0] for      -- 4
[1] for      @@ 4
[0] for      -- 5
[1] for      @@ 5
  C-c C-cINFO:ioknife.too:send signal (Signals.SIGINT)