Upgrade to Pro — share decks privately, control downloads, hide ads and more …

mrubyでワンバイナリーなテキストフィルタツールを作った / Building Text F...

buty4649
January 18, 2025
59

mrubyでワンバイナリーなテキストフィルタツールを作った / Building Text Filtering Tools with mruby #tokyorubykaigi

東京Ruby会議12で発表した資料です。 https://regional.rubykaigi.org/tokyo12/
私の作っているrfコマンドの紹介、mrubyの基本的な情報、使い方、開発するときのTipsを紹介しています。

buty4649

January 18, 2025
Tweet

Transcript

  1. 2 自己紹介 GMOペパボ株式会社 プリンシパルエンジニア 2014年 中途入社 高谷 雄貴 Yuki Koya

    いわゆるインフラエンジニア。プライベートクラウドや 社内システムの面倒をみていたが今は育休中。 • GitHub: buty4649 • mixi2: @buty4649 • X(旧Twitter): @buty4649 • Blog: https://tech.buty4649.net
  2. rfコマンドの紹介 9 rubyコマンドの起動オプションを工夫するとワンライナーで実行できる Rubyでテキストフィルタしたい # grep foo と同等の処理をする例 $ ruby

    -ple 'next if ! /foo/' hoge.txt # sed -i.bak 's/foo/bar/g'と同等の処理をする例 $ ruby -i.bak -pe 'gsub(/foo/,"bar")' hoge.txt # jq '.[].foo'と同等の処理をする例 $ ruby -rjson -e 'p JSON[ARGF.read].dig(0,"foo")' hoge.json
  3. rfコマンドの紹介 10 rubyコマンドの起動オプションを工夫するとワンライナーで実行できる Rubyでテキストフィルタしたい # grep foo と同等の処理をする例 $ ruby

    -ple 'next if ! /foo/' hoge.txt ←—------- タイプ数は27 # sed -i.bak 's/foo/bar/g'と同等の処理をする例 $ ruby -i.bak -pe 'gsub(/foo/,"bar")' hoge.txt ← タイプ数は35 # jq '.[].foo'と同等の処理をする例 $ ruby -rjson -e 'p JSON[ARGF.read].dig(0,"foo")' hoge.json ↑ タイプ数は47
  4. rfコマンドの紹介 14 • 私が作っているテキストフィルタツール ◦ Ruby Filterの略 ◦ 名前の短さもタイプ数を考慮 ◦

    名前のとおりRubyでフィルタリング処理が書ける • 出力時に色付けを自動で行い視覚的に見やすく • プレーンテキストはもちろんJSONやYAMLにも対応 • 独自の拡張でタイプ数を極力減らしている • ワンバイナリ構成でマルチプラットフォーム対応 rfコマンドとは?
  5. rfコマンドの紹介 15 rubyコマンドのワンライナーの例をrfコマンドで置き換えると rfコマンドの実行例(grep, sed相当) # grep foo と同等の処理をする例 $

    rf /foo/ hoge.txt ←ーーーーーーーーーー タイプ数が27から9 foobar barfoo # sed -i.bak 's/foo/bar/g'と同等の処理をする例 $ rf -i.bak 'gsub(/foo/,"bar")' hoge.txt ← タイプ数が35から29 $ cat hoge.txt barbar barbar
  6. rfコマンドの紹介 19 Usage: rf [入力データの形式] [オプション] <フィルタ> [入力ファイル...] • 入力データの形式

    ◦ -t: プレインテキスト(省略可)、-j : JSON、-y YAML • オプション ◦ 再帰検索(-r)やファイルの上書き保存(-i)などなど • フィルタ ◦ Rubyでフィルタを定義 • 入力ファイル ◦ 複数指定可能、省略した場合は標準入力 rfコマンドの使い方
  7. rfコマンドの紹介 20 awkやruby -a -nとだいたい同じ 1. 入力されたデータをrf内蔵のフィルタを通して分割する a. プレーンテキストであれば一行ごとに分割 b.

    分割したデータをrfではレコードと呼んでいる c. レコードはさらにスペースで分割される(rfではフィールドと呼んでいる) 2. レコードごとにイテレーション処理する a. レコードを入力としてRubyのコードを実行する b. 評価結果を加工して出力 i. プレーンテキストであればto_s ii. JSON/YAMLであればto_json / to_yaml rfの処理の流れ
  8. rfコマンドの紹介 21 レコードやフィールドはよく使うので特殊な変数として追加 • レコードは _ (アンダーバー)に格納されている ◦ ruby -nにおける

    $_ と同等だが1文字削減 • フィールドは _1, _2, …(アンダーバー + 数字)に格納されている ◦ Ruby本来の構文では番号指定パラメータとして機能する • 具体例: 入力が a␣b␣c だった場合 ◦ _ : a␣b␣c ◦ _1 : a ◦ _2 : b ◦ _3 : c rfコマンドの独自拡張(特殊変数)
  9. rfコマンドの紹介 22 ワンライナーで初期化処理をなくしタイプ数を減らす • 未定義な変数に対して加算代入 ◦ s+=1やs+="hello"みたいなことができる ▪ 通常なら undefined

    method `+' for nil ◦ Arrayも加算代入できるがHashはできない ▪ Hashには+メソッドがないため • 例えば数値の集計をしたいときに使う ◦ rf 's+=_1.to_i' sum.txt ◦ s||=0は不要 rfコマンドの独自拡張(未定義な変数への加算)
  10. rfコマンドの紹介 23 プレーンテキストでは入力はすべてStringのため数値として扱うにはto_i/to_fが必要 タイプ数を減らすためrfではStringからInteger/Floatへ暗黙的な変換がされる • 具体例 : 1 + "1"

    ◦ 通常なら String can't be coerced into Integer ◦ rfだと 2 • 例えば数値の集計時に便利 ◦ rf 's+=_1' sum.txt ◦ _1にto_iが不要 rfコマンドの独自拡張(StringのInteger/Floatへの暗黙的な変換)
  11. mrubyとは? 26 • 軽量Rubyと呼ばれる実装 • Ruby(CRuby,MRI)と比べてメモリ使用量が少ない • 必要な機能のみmrubyに組み込むことができる ◦ mrbgemsというパッケージを追加することで機能を追加できる

    • 組み込み用途で利用されることが多い ◦ IoT ◦ ミドルウェア • 基本的にはmrubyをコンパイルするとライブラリが生成される ◦ libmruby.aといったファイルができる mrubyとは?
  12. mrubyとは? 27 CRubyに比べて制約がある • 標準ライブラリが少ない ◦ 例えば標準ではJSONやYAMLのライブラリがない • 使えない機能がある ◦

    Kernel#require、スレッドなど • public、private、protectedで違いがない • 詳しくは以下を参照のこと ◦ https://github.com/mruby/mruby/blob/master/doc/limitations.md mrubyにおける制約
  13. mrubyとは? 28 まずは素のmrubyをビルドしてみる • ビルドするためのツールをインストール ◦ gcc または clang など

    ◦ rakeコマンド • https://github.com/mruby/mruby/ からgit cloneする ◦ 頻繁に更新されているのでGitHubから持ってくるのがおすすめ • mrubyディレクトリに入りビルドする ◦ $ cd mruby && rake • build/host/bin配下にmrubyやmirbコマンドなどが作成される ◦ mruby関連のコマンドはprefixにmが付く mrubyの始め方
  14. mrubyとは? 29 • mrubyのビルドコマンドやmrbgemsの追加を行う ◦ Rubyで定義を書く ◦ クロスコンパイルの定義もここで行う • デフォルトではmrubyディレクトリ配下のbuild_config/defaut.rbを使用

    • default.rbでは以下のようになっている ◦ gccまたはclangを使ってビルド ◦ クロスコンパイルしない ◦ 標準のmrbgemsを使用 ◦ デバッグは無効、テストは有効、バイナリテストも有効 build_config.rbを使ってカスタマイズする
  15. mrubyを使ったCLIツールの作り方 31 mrbgemsでspec.binsを指定するとCLIツールが作れる 具体例としてはmrubyコマンドやmirbコマンド これらもmrbgemsとして分離されている CLIツールの作り方 # mruby-bin-mirbのmrbgems MRuby::Gem::Specification.new('mruby-bin-mirb') do

    |spec| spec.license = 'MIT' spec.author = 'mruby developers' spec.summary = 'mirb command' —- snip —- spec.bins = %w(mirb) # <- ここで指定している spec.add_dependency('mruby-compiler', :core => 'mruby-compiler') end ※https://github.com/mruby/mruby/blob/master/mrbgems/mruby-bin-mirb/mrbgem.rake
  16. mrubyを使ったCLIツールの作り方 32 • 必ずmrbgem.rakeを作らないといけない ◦ メタ情報やビルドコマンドをRubyで定義 • RubyまたはC/C++で記述できる ◦ Rubyはmrblib配下、C/C++はsrc配下

    • C/C++の場合は初期化関数、終了関数の定義が必要 ◦ mrb_<mrbgem名>_gem_init ◦ mrb_<mrbgem名>_gem_final • CLIツールの場合はtools配下に配置 mrbgemsの作り方 ※https://github.com/mruby/mruby/blob/master/doc/guides/mrbgems.md # ディレクトリ構成 <root> |-- mrbgem.rake | |-- mrblib | |-- foo.rb | |-- src | |-- foo.c | |-- tools |-- foo |-- main.c
  17. mrubyを使ったCLIツールの作り方 33 • mrubyにはAPIがあり、mruby VMを作成・操作できる • APIを使うにはmruby.hをincludeしビルド時にlibmrubyにリンクする • 最低限知っておくとよいAPI ◦

    mrb_open : mruby VMを作成 ◦ mrb_funcall : mrubyのメソッドを実行 ◦ mrb_close : mruby VMを破棄 • C/C++のmain関数からmrubyのメソッドを呼びだすことでCLIツールを実現 C/C++からmrubyを利用する
  18. mrubyを使ったCLIツールの作り方 34 #include<mruby.h> int main(int argc, char* argv) { //

    mruby VMの作成 mrb_state* mrb = mrb_open(); // mrubyのmainメソッドを実行 mrb_funcall(mrb, mrb_top_self(mrb), "main", 0, NULL); // mruby VMの破棄 mrb_close(mrb); return 0; } C/C++からmrubyを使う例 def main … # CLIツールの実装を書く … end C/C++のコード mrubyのコード
  19. mrubyを使ったCLIツールの作り方 35 • mrb_eval : 指定された文字列をmruby VM上でevalする • mrb_str_new_cstr :

    指定されたCの文字列をmrubyのStringに変換する • mrb_ary_new : Array.newを行う • mrb_ary_push : Array#pushを行う • mrb_hash_new : Hash.newを行う • mrb_hash_set : Hash#[]=を行う (参考)よく使うmruby API
  20. mrubyで開発するときのTips 39 • build_config.rbでenable_testを有効にする ◦ mruby-testが自動的に追加される ◦ rake testするとtestディレクトリ配下が実行される •

    mrubyの仕様上テスト時にもバイナリビルドされる ◦ バイナリビルドは遅い ◦ rfではenable_testを無効にしている ◦ 代わりにrspecとcucumber/arubaを利用 ▪ cucumber/arubaはCLIテストフレームワーク ▪ https://github.com/cucumber/aruba テストについて
  21. mrubyで開発するときのTips 40 GitHub Actionで各環境のテストができる 右図はrfの設定の抜粋(※) マルチプラットフォーム環境でのテストについて jobs: build: runs-on: ubuntu-latest

    strategy: matrix: os-arch: [linux-amd64, darwin-arm64, windows-amd64] steps: - run: rake build:${{ matrix.os-arch }} - uses: actions/upload-artifact@v4 test: needs: build strategy: matrix: runs-on: [ubuntu-latest, macos-14, windows-latest] include: - runs-on: ubuntu-latest os-arch: linux-amd64 - runs-on: macos-14 os-arch: darwin-arm64 - runs-on: windows-latest os-arch: windows-amd64 runs-on: ${{ matrix.runs-on }} steps: - uses: actions/download-artifact@v4 - name: Run test run: bundle exec rake spec ※https://github.com/buty4649/rf/blob/main/.github/workflows/ci.yml
  22. mrubyで開発するときのTips 41 • build_config.rbにenable_debugを有効にする ◦ ビルド時に-g3 -O0が追加される ▪ リリースビルド時には無効にする(大切) ◦

    Ruby部分の例外発生時にバックトレースが出力される • コードによってデバッグツールが変わる ◦ C/C++のコードはgdbを使う ◦ Rubyのコードはmrdbを使う ▪ https://github.com/mruby/mruby/blob/master/doc/guides/debugger.md デバッグについて
  23. mrubyで開発するときのTips 42 • 通常のRuby開発と同じ設定を使いまわせる ◦ Ruby LSP ◦ rubocopもそのまま使える •

    C/C++の設定 ◦ Include Pathにmruby.hのディレクトリを指定 ▪ build/host/includeも追加しないとpresym.h(※)が参照できない ◦ ビルドタスクの構成にrake allの実行を追加 ◦ デバッグの構成にgdbを追加 ▪ デバッグ実行時にrake allタスクを実行するように設定 ▪ mrdbを使ったデバッグは研究中… VSCodeのおすすめ設定 ※https://github.com/mruby/mruby/blob/master/doc/guides/symbol.md
  24. mrubyで開発するときのTips 43 # .vscode/settings.json: C/C++のInclude Pathの設定 { "C_Cpp.default.includePath": [ "${workspaceFolder}/mruby/include",

    "${workspaceFolder}/build/host/include" ] } # .vscode/settings.json: ビルドタスクの構成 { "version": "2.0.0", "tasks": [ { "label": "rake build", "type": "shell", "command": "rake build" } ] } VSCodeの設定例 # .vscode/launch.json: デバッグの構成 { "version": "0.2.0", "configurations": [ { "name": "gdb", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/bin/rf", "args": [], "cwd": "${workspaceRoot}", "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "preLaunchTask": "rake build" } ] }