前回はこちら。 前回ブログを書いたのが去年の12/23だったので、10ヶ月ぶりのブログにする! その間にもほそぼそと更新していてv1.24.0からv1.29.0になったので主な更新点を紹介。 なお、このブログ記事を書いている間にv1.31.0をリリースしてしまったのでそれは別で記事にします…。
[!NOTE] rfコマンドアドベントカレンダーを作りました!Tipsなんかを書いていくはず…はず https://qiita.com/advent-calendar/2025/rf_command
主な更新点
- CLIフレームワークをmagniに変更
- フィルタタイプの切り替えをオプション指定からサブコマンドに移行
- 複数回-eオプションを指定可能に変更
- --grepオプションを廃止してgrepサブコマンドに移行
- JSON <-> YAMLの変換が簡単にできるようになった
- Array/HashをMarkdownテーブルに変換できるようになった
- レシーバーをContainerからRecordに変更
- バイナリマッチングの処理方法を変更
- OnigRexexp#onの追加
CLIフレームワークをmagniに変更
いままでrfは独自フレームワークを使っていたのだが、自作のCLIフレームワークmagniに変更した。
magniについては前回ブログで書いたとおり。
本当のことを書くとmagniを作ったからrfも乗り換えたというわけではなくて、rfに機能を追加したくてmagniを作ったというのが正しい。
後述するのだけどrfの新機能で複数回オプションを指定できるようにしたくなったのだけど、当時のオプションパーサー周りを書き換えようとすると大規模な変更が必要となり、そのためにゴリゴリコードを変更するのは…っとなり、それならばっとCLIの関心ごとを分離してmgemにしたほうがいいだろうとなってできたのがmagniだった。
実際、rfのためにmagniを作ったので出来は気に入っている。 必要な機能があればmagniに追加すれよいし、関心ごとの範囲が狭まるのでとてもよい。 magniに変えて良いことばかりではなく、magniはThorを真似て作っているのでサブコマンド方式になっている。 これによりrfのオプション指定が変わってしまったというのが次の更新点。
フィルタタイプの切り替えをオプション指定からサブコマンドに移行
magniに変えたことで今までフィルタのタイプを--json(--type json)や--yaml(--type yaml)で指定していたものを、サブコマンドで指定することになった。 具体的には以下の通り。
# rf 1.25.0までのJSONフィルタの指定 $ rf --json _.size test.json $ rf -j _.size test.json # 短縮形も指定できる # rf 1.26.0からのJSONフィルタの指定 $ rf json _.size test.json $ rf j _.size test.json # 短縮可能
こうしてみるとサブコマンド化したことで -(ハイフン)が不必要になり、むしろタイプ数が減ったのでは?!っとなったのでこの形式を採用した。 とはいえ、READMEなどのドキュメントを直すハメになったのだった…*1
複数回-eオプションを指定可能に変更
magniに変更して一番やりたかった変更がこれ。 要はsedにおける-eオプションと同じことをしたいというのがモチベーション。 たとえば、ログファイルを特定の時間で抽出してかつ時間部分を削って出力するとか、特定のIPレンジのインターフェイスを持つサーバを列挙してそのホスト名を装飾して出力するとか。
# ChatGPTに生成させたsyslogのサンプル $ cat sample.log Jan 25 10:14:32 web01 systemd[1]: Started Session 3245 of user deploy. Jan 25 10:14:33 web01 sshd[18342]: Accepted publickey for deploy from 203.0.113.24 port 51432 ssh2 Jan 25 10:14:33 web01 sshd[18342]: pam_unix(sshd:session): session opened for user deploy by (uid=0) Jan 25 10:14:35 web01 sudo[18367]: deploy : TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/bin/systemctl restart nginx Jan 25 10:14:36 web01 systemd[1]: nginx.service: Starting service... Jan 25 10:14:36 web01 systemd[1]: nginx.service: Started nginx - high performance web server. Jan 25 10:15:01 web01 CRON[18410]: (root) CMD (/usr/local/bin/backup --incremental) Jan 25 10:15:32 web01 kernel: [ 4234.112312] eth0: link up, 1000Mbps, full-duplex, lpa 0xC5E1 Jan 25 10:16:02 web01 kernel: [ 4264.813551] EXT4-fs (vda1): re-mounted. Opts: errors=remount-ro # Jan 25 10:15のCRONログだけ出力する $ rf -e '/Jan 25 10:15:01/' -e '/CRON/' sample.log Jan 25 10:15:01 web01 CRON[18410]: (root) CMD (/usr/local/bin/backup --incremental)
…どちらも正規表現を書けばできてしまう気がするが、-eで分割することで複雑になりすぎなくなるはず。
--grepオプションを廃止してgrepサブコマンドに移行
コマンド引数をRubyのコードではなく正規表現として解釈して、それにマッチしたレコードのみ出力するという--grepオプションがあった。 テキストはもちろんのこと、JSONやYAMLでも使えたのだが、rfを使っていてJSON/YAMLで--grepを使う場面がなかったので、オプションから分離してサブコマンドに変更した。
# 1.25.0までは--grepオプションだった $ rf --grep sudo sample.log Jan 25 10:14:35 web01 sudo[18367]: deploy : TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/bin/systemctl restart nginx # 1.27.0以降はサブコマンドになった $ rf grep sudo sample.log Jan 25 10:14:35 web01 sudo[18367]: deploy : TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/bin/systemctl restart nginx
ちなみに、手元のマシンでは alias grep="rf grep" して使っている。これで足りない機能は順次追加している。ドッグフーディング重要。
JSON <-> YAMLの変換が簡単にできるようになった
to_json/to_yamlでオブジェクトをjson/yamlに変換できていたのだが、json/yamlフィルタを使ってファイルを読み出しているときにそのままto_json/to_yamlしてしまうと、それぞらのファーマットにおける文字列として出力されていた。 これはフィルタが出力する形式への変換も担っていたことに起因していたので、フィルタから形式変換する部分を切り出し新たにformatterという層を作った。 これによりto_json/to_yamlしたときにフィルタに依存せずに出力形式を選べるようになった。
$ cat hash.json { "foo": 1, "bar": { "baz": [ "a", "b", "c" ] }, "foo bar": "foo bar" } # 1.28.0まではputs _.to_yamlみたいにする必要があった $ rf --json -q 'puts _.to_yaml' hash.json --- foo: 1 bar: baz: - a - b - c foo bar: foo bar # 1.29.0からはto_yamlだけで変換できるようになった $ rf json to_yaml hash.json foo: 1 bar: baz: - a - b - c foo bar: foo bar
Array/HashをMarkdownテーブルに変換できるようになった
formatter層導入により自由に出力形式を増やすことができるようになったので、前から欲しかったMarkdownのテーブル形式で出力する機能を追加した。 ArrayやHashをto_tableするとよしなにMarkdownにできる、便利〜。
$ cat array.json [["Name", "Age"], ["Alice", 30], ["Bob", 25]] # Array#to_table, Hash#to_tableでMarkdownテーブルに変換 $ rf json -s to_table array.json | Name | Age | | ----- | ----- | | Alice | 30 | | Bob | 25 |
Rexexp#onの追加
パーサーの解釈が厳密になったために m /foobar/ { hoge }のような書き方ができなくなってしまった。
この書き方はfoobarにマッチした行のみ選択して、hogeという処理を行うコードでよく使っていた。
これがm(/foobar/){ hoge }と書かないといけなくなり、タイプ数は変わらないものの()をタイピングするのが面倒なのでどうにかしたかった。
そこで正規表現にマッチした行のみ選択するというところに着目してRegexp#onを追加した。
これにより/foobar/.on { hoge }っとかけるようになった。
# mrubyのパーサーの変更によりエラーになった $ echo "foobar hoge fuga" | rf 'm /foobar/ { _ } ' Error: line 1: syntax error, unexpected '{', expecting end of file # Regexp#onによりエラーなく記述できる $ echo "foobar hoge fuga" | rf '/foobar/.on { _ }' foobar hoge fuga
バイナリマッチングの処理方法を変更
バイナリを含んだ入力の場合、そのまま出力するとターミナルがおかしくなるので、それを回避するためにbinary matchと表示して内容は表示しないようにしている(grepもそうなっている)。 今までは制御文字を含んだ人間の読めない文字をバイナリとして扱っていたのだが、エスケープシーケンス(\e)もバイナリとしてマッチしてしまうと使い勝手が悪かったので、入力を強制的にUTF-8に変換してinvalidだった場合にバイナリとすることにした(たぶん、grepと同じ処理)。
# 1.25.0まではエスケープシーケンスもバイナリ判定だった $ echo -e "\e[31mA\e[m" | mise exec rf@1.25.0 -- rf _ Binary file matches. # 1.28.0からはエスケープシーケンスはバイナリ判定にならない $ echo -e "\e[31mA\e[m" | rf _ A # NULL文字などは相変わらずバイナリ判定する $ echo -e "\e[31mA\0\e[m" | rf _ Warning: (stdin): binary file matches $ echo -e "\e[31mA\xff\xfe\e[m" | rf _ Warning: (stdin): binary file matches
レシーバーをRf::ContainerからRf::Recordに変更
少しわかりづらいのだけど、今までフィルタにしていたコマンドのレシーバーはRf::Containerのインスタンスになっていた。これをRf::Recordのインスタンスに変更した。 この変更で何が嬉しいかというと、レコードに対してgsubなどを実行したいときに、いちいちレシーバーを明示しないといけなかったのが不要になる。
# before $ echo abc | rf '_.gsub(/abc/, "def")' def # after $ echo abc | rf 'gsub(/abc/, "def")' def
実はgsubなどのよく使うであろうメソッドは、今回の変更をする前からdelegationしていたので、レシーバーなしでアクセスできていたのだけど、一部のメソッドだけでいざ他のメソッドを使いたくても使えない!ということがあったので、変更した次第。
更新点(詳細)
v1.25.0
- Add OnigRegexp#on method by @buty4649 in https://github.com/buty4649/rf/pull/307
- Update mruby by @buty4649 in https://github.com/buty4649/rf/pull/306
v1.26.0
- Use buty4649/magni by @buty4649 in https://github.com/buty4649/rf/pull/310
- Use tagpr by @buty4649 in https://github.com/buty4649/rf/pull/312
- Improve tagpr labels by @buty4649 in https://github.com/buty4649/rf/pull/313
- Rename workflow by @buty4649 in https://github.com/buty4649/rf/pull/314
- Fix Docker Hub authentication in release workflow by @buty4649 in https://github.com/buty4649/rf/pull/316
- Disable release for tagpr by @buty4649 in https://github.com/buty4649/rf/pull/318
v1.27.0
- Pin GitHub Actions to commit hashes for security by @buty4649 in https://github.com/buty4649/rf/pull/320
- Improve error handling and help display by @buty4649 in https://github.com/buty4649/rf/pull/322
- Add -e option (multiple expr support) and remove --disable-boolean-mode by @buty4649 in https://github.com/buty4649/rf/pull/323
- Replace --grep option with grep command by @buty4649 in https://github.com/buty4649/rf/pull/324
v1.28.0
- Update magni by @buty4649 in https://github.com/buty4649/rf/pull/325
- Add invert match (-v) option to grep mode by @buty4649 in https://github.com/buty4649/rf/pull/327
- Fix binary match processing and add comprehensive tests by @buty4649 in https://github.com/buty4649/rf/pull/330
- Implement dynamic method delegation in Container by @buty4649 in https://github.com/buty4649/rf/pull/331
- Improve help message by @buty4649 in https://github.com/buty4649/rf/pull/332
v1.28.1
- Fix file open mode to prevent permission errors by @buty4649 in https://github.com/buty4649/rf/pull/333
v1.29.0
- feat: add cross-format data conversion capabilities by @buty4649 in https://github.com/buty4649/rf/pull/336
- Refactor Rf::Filter class by @buty4649 in https://github.com/buty4649/rf/pull/338
- feat: implement to_table formatter for Array and Hash by @buty4649 in https://github.com/buty4649/rf/pull/339
*1:AIがシュッと直してくれるのでそこまで手間ではない