Ruby: 文字列の書式設定では引用符内で書式設定以外の`%`記号を`%%`とエスケープすること

ある日のSlackにて 前振り1: Rubyの%記号と式展開 今回の記事を書くために、まず%(パーセント)記号と式展開についてそれぞれ別記事にまとめましたので先に以下をご覧ください。そうしないと自分が混乱してしまうので。 Ruby: パーセント記号 `%` の使い方まとめ Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?` 前振り2: sprintfやString#%メソッドを使う場合の%文字のエスケープ この部分はRubyのバージョンに限らず共通です。 RubyのsprintfやString#%メソッドを用いて書式設定を行う場合、引用符内で%そのものを出力したい場合は%%とすることでエスケープされます(エラーメッセージでも%%を使えと書かれてますね)。面白いことに、%%による%のエスケープは二重引用符でも一重引用符でも使えます。本記事では簡単のためString#%メソッドのみを用いています。 ‘%0.0f%%’ % 45 #=> “45%” “%0.0f%%” % 45 #=> “45%” # 機能と関係ない%はエスケープしないとエラーになる ‘%0.0f%’ % 45 #=> ArgumentError: incomplete format specifier; use %% (double %) instead 逆に、バックスラッシュ%では二重引用符/一重引用符にかかわらず、%をエスケープできません。 “%0.0f%” % 45 #=> ArgumentError: incomplete format specifier; use %% (double %) instead `%0.0f%` % 45 #=> ArgumentError: incomplete format specifier; use %% (double %) instead ただしsprintfやString#%メソッドを使っていなければ、二重引用符内で通常どおり%でエスケープできます。もちろん一重引用符では式展開もエスケープも無効になります。おなじみの挙動ですね。 value = 0.0 “#{value}%” #=> “0.0%” ‘#{value}%’ #=> “#{value}\%” (一重引用符なのでエスケープされない) 前振り3: SQLのLIKEで使われる% さらにややこしいのが、SQLのLIKEでは%を一種のワイルドカード的に使えるという点です。 SELECT id FROM users WHERE name LIKE ‘%name%’; 上はnameカラムが「なんちゃらnameなんちゃら」の行を取得します。「なんちゃら」は長さゼロも含みます。 Ruby 2.5で何が変わったのか いろいろ書きましたが、Ruby 2.5で引用符内の%の挙動が変わったのはsprintfに関連する場合です(String#%で書式を設定する場合を含む)。 とりあえず以下の3つの場合が考えられます(簡単のためString#%の書式に揃えました)。 # 1. 名前なし/順序のみの引数指定 ‘%%%d%%’ % 99 #=> “%99%” # 2. 名前付き変数のハッシュでの指定(書式あり) ‘%%%<value>d%%’ % { value: 99} #=> “%99%” # 3. 名前付き変数のハッシュでの指定(書式なし) ‘%%%{value}%%’ % { value: 99} #=> “%99%” 2.と3.の名前付き変数はハッシュ{ 変数: 値}で渡すことになっています。また、3.の%{名前付き変数}では書式は設定できないので、実は普通の式展開でやっても同じだったりします。詳しくは以下の記事をご覧ください。 Ruby: パーセント記号 `%` の使い方まとめ ここまでを押さえたところで、冒頭のSlackに出てきたRubyコードを再録します。 Ruby 2.4.x以前の場合 ‘%%%{text}%’ % { text: “aaa” } %は左から順に以下の意味になります。 %%: SQLで使う %{text}’: 式展開の一種({ }なので書式は指定できない) %: SQLで使う %: String#%メソッド(書式設定用) 上ではSQLで使う%%や%がどちらも使えてしまっている点にご注目ください。後述のツイートにあるように、これは本来の挙動ではありませんでした。ここでは%の後ろがたまたま%や’だったおかげで、%が書式設定用ではないとRubyに認識されただけと考えられます。 したがって、以下のようにエスケープなしの%の後ろに文字があるとエラーになります。 ‘%%%{text}% decimal’ % { text: “aaa” } #=> ArgumentError: unnumbered(1) mixed with named ‘%%%{text}%です’ % { text: “aaa” } #=> ArgumentError: unnumbered(1) mixed with named つまり、String#%で書式を整える場合は、Rubyのバージョンにかかわらず、エスケープなしの%を引用符に書かない方がよいということになります。引用符内の単独の%は、Rubyのバージョンや文字列内の位置で挙動が変わるので当てにしない方がよいと思います。 Ruby 2.5以降の場合 ‘%%%{text}%%’ … Continue reading Ruby: 文字列の書式設定では引用符内で書式設定以外の`%`記号を`%%`とエスケープすること