Rubyでプログラム開発に取り組んだことがある方は「eval」というメソッドが利用されているコードを見掛けたことがあるのではないでしょうか。
本記事では、Ruby開発初心者の方にも分かりやすく、evalメソッドの役割と使い方をサンプルコードを掲載しながらご紹介していきます。
Rubyのevalメソッドとは
Rubyのevalメソッドとは、引数に設定した文字列をRubyのプログラムとして実行するメソッドです。
evalはメタプログラミングで使用
evalメソッドは少し専門的な言葉で表すと、メタプログラミングで利用するメソッドです。
メタプログラミングとは、Wikipediaによると下記のように記述されています。
プログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。
evalメソッドを使うメリットは?
evalメソッドを使うメリットは、簡単に言ってしまうと何でも出来る点にあります。
与えられた文字列をRubyのプログラムとして解析してくれるため、言ってしまえばRubyで出来ることは全て実現出来ることになります。
またevalメソッドでは、指定した文字列がプログラムコードとして正しいかどうかを評価するため、実行するプログラムのエラーチェックとして利用することが可能です。
evalメソッドを使う危険性も理解しよう
evalメソッドは何でも出来る反面、危険性の高いメソッドとしても知られています。
最も危険視すべき点は、開発者が予測出来ていないバグを生み出す可能性が非常に高いということです。
プログラムコードとして成立している文字列であれば何でも実行出来てしまうため、例えばユーザーが入力した文字列をプログラムとして解析するような実装をしている場合、悪意のあるユーザーによってシステムが破壊されてしまう可能性も十分に考えられます。
他にもevalを利用することによって、パフォーマンスの低下や可読性の低下を招く危険もあるため、使い所を慎重に見極めた上で利用する必要があります。
Rubyでのevalメソッドの使い方
では実際にRubyのevalメソッドはどのように記述して利用出来るのか確認していきましょう。
基本構文
Rubyのevalでは、下記の構文に沿って記述します。
eval(expr [, bind, fname, lineno])
第1引数の「expr」は必須項目で、第2引数以降の値は任意で設定可能です。
第1引数: expr
Rubyプログラムとして評価したい文字列を第1引数のexprに設定します。
「サンプルを出力しています。」とコンソール出力したい場合には、下記のように記述します。
eval('puts "サンプルを出力しています。"')
実行結果
$ ruby sample.rb サンプルを出力しています。
第2引数: bind
bindはBindingオブジェクトの略称で、変数やメソッドなどの環境情報を表すオブジェクトです。
あるメソッド内のローカル変数を、evalで参照したい場合などに利用出来ます。
エラーケース
例えば下記の記述はエラーとなってしまいます。
eval('text="サンプルを出力しています。"') eval('puts text')
実行結果
$ ruby sample.rb Traceback (most recent call last): 2: from sample.rb:2:in `' 1: from sample.rb:2:in `eval' (eval):1:in `': undefined local variable or method `text' for main:Object (NameError)
最初のeval内で設定した変数を保持したまま、別のeval処理で利用することは出来ないようです。
Bindingオブジェクトを活用した記述方法
上記のエラーをBindingオブジェクトを利用して記述し直すと、下記のように修正することが出来ます。
def local_variable text="サンプルを出力しています。" binding end eval('puts text', local_variable)
実行結果
$ ruby sample.rb サンプルを出力しています。
今回の場合、local_variableというメソッドを作成しbindingオブジェクトを返却するように記述したことで、ローカル変数として定義されたtextにevalからアクセスすることが出来るようになりました。
第3引数: fname
fnameにファイル名を指定することで、exprに記述された文字列を指定したファイル名で実行されているかのように見せかけることが出来ます。
例えば、実際にはsample.rbに記述されていた上記のRubyプログラムを、test.rbから実行されているかのように見せかけてみましょう。
def local_variable text="サンプルを出力しています。" binding end eval('put text', local_variable, 'test.rb')
実行結果
$ ruby sample.rb test.rb:1:in `local_variable': undefined method `put' for main:Object (NoMethodError)
今回のサンプルでは、本来putsと記述しなければいけない箇所をputと記述し、わざとエラーを発生させています。
実行結果にはtest.rbファイルの1行目でエラーが発生したかのように表示されています。
第4引数: lineno
linenoは第3引数と関連して、exprに記述された文字列をどのファイルの何行目かを指定して実行されているかのように見せかけることが出来ます。
例えば第3引数の記述方法では、エラー発生箇所が1行目と表示されていましたが、第4引数に5と設定して改めて実行してみましょう。
def local_variable text="サンプルを出力しています。" binding end eval('put text', local_variable, 'test.rb', 5)
実行結果
$ ruby sample.rb test.rb:5:in `local_variable': undefined method `put' for main:Object (NoMethodError)
実行結果のエラー発生箇所が5行目と表示されていることをご確認頂けます。
さいごに:Rubyのevalメソッドは安易に使わないように!
本記事では、Rubyのevalメソッドについて、メソッドの役割から簡単な使い方をサンプルを交えてご紹介してきました。
evalは非常に強力なメソッドであるため、使い方によっては深刻なバグに繋がる可能性も常に意識しておかなければいけません。
evalを利用しなくても任意のプログラムを実現出来る方法が存在するケースは多く、まずはRubyのデフォルトやフレームワークで置き換えられるメソッドが提供されていないかを確認してみましょう。
もしevalを利用しないと実現することが難しい場合には、思わぬバグを紛れ込ませないように入念に正当性チェックを行った上で利用するようにしてください。
第3引数のfnameを省略した場合にはデフォルト値して「(eval)」、第4引数のlinenoを省略した場合にはデフォルト値として「1」が設定されます。