Quantcast
Channel: hachi8833の記事一覧|TechRacho by BPS株式会社
Viewing all articles
Browse latest Browse all 1765

Ruby: パーセント記号 `%` の使い方まとめ

$
0
0

Ruby公式ドキュメントに記載されている`%(パーセント)記号の説明の中から、以下についてまとめました。なお、検証にはRuby 2.6.1を使いました。

  1. 剰余(割り算の余り)を表す %
  2. %記法リテラル記法)で使う %
  3. 出力フォーマット(書式設定)で使う %
    • printf%
    • String#%メソッド

参考: Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 2.6.0)


なお公式ドキュメントにある% ruby -e "puts 'Hello'"という%の用い方は説明文向けの記法であり、それが「コマンドラインへの入力を表す」という目印でしかありません(実を言うと実際に見かけた記憶がありませんが)。

ちょうど、説明文でのみインスタンスメソッド名に#を付けて#to_sのように書き、実際のRubyスクリプトではそう書かないのと似ています。

1. 剰余の%

剰余演算を表す%は比較的単純です。レシーバー(左辺)と引数(右辺)が数値の場合に剰余演算子と解釈されます。一方が複素数の場合はさすがに未定義エラーになります。

12 % 5     #=> 2

12.0 % 5   #=> 2.0

12.0 % 5.1 #=> 1.8000000000000007

12 % 5.1   #=> 1.8000000000000007

12r % 5r   #=> (2/1)

Complex(3.14, 0) % Complex(3.14, 0) #=> NoMethodError (undefined method `%' for (3.14+0i):Complex)

2. %記法の%

ここから少々ややこしくなります。

Rubyでは、%qなどのように%の後ろの1文字で機能を指定し、それに続く記号ペアで囲むことでさまざまなリテラルを表現できます。

囲みに使う記号ペアは、{}[]のように対になっているものも、!!のように同じ記号を使うこともできます。

%q<Hello>  #=> "Hello"   -- 対になる記号で囲んだ場合

%q!World!  #=> "World"   -- 同じ記号で囲んだ場合

%記法で記号ペアを適宜カスタマイズすると、たとえば二重引用符"と一重引用符'が混在するメッセージで自動的に引用符をエスケープしてくれます。エスケープを書かずに済むので便利です。

%q{Warning: "invalid character '%' has been found" }
#=> "Warning: \"invalid character '%' has been found\" "

その気になればバッククォートやバックスラッシュ、%自身すら囲み記号として使えます。

%q`World`   #=> "World"
%q\World\   #=> "World"
%q%World%   #=> "World"

ただし記号ペアに使えるのはASCIIの記号に限るようです。以下は全角疑問符でエラーになった例です。

%q?World?  #=> SyntaxError ((irb):88: unknown type of %string)

%記法の機能一覧

参考: Ruby Programming/Syntax/Literals - Wikibooks, open books for an open world

以下の説明では簡単のため囲みを[ ]で代表しています。上述のとおり、他にもさまざまなASCII記号を囲みに使えます。

なお、Rubyの式展開(string interpolation)は、二重引用符" "で囲まれた文字列リテラル内に#{ 式 }の形で式を記述することで、式が評価された結果を展開できる機能です。一重引用符' 'では式は展開されません。

以下にいくつもの記法がありますが、「式展開できるか」「\でエスケープしたり特殊文字を置けるか」の2点に注目すると理解しやすいと思います。


%[ ]%Q[ ]でも同じ)による二重引用符文字列リテラルの代替

表記 意味 式展開 その他
%[ ]
%Q[ ]
二重引用符" "で囲むのと同等 できる \でエスケープや特殊文字を表せる
  • 二重引用符の文字列リテラルと同様、式展開が使えます。
%[Time: #{Time.new}]  #=> "Time: 2019-02-27 16:35:58 +0900"  -- 式展開できる
%Q[Time: #{Time.new}] #=> "Time: 2019-02-27 16:35:58 +0900"  -- 式展開できる
  • \nで改行を表すことも、囲み文字を\[\]のようにエスケープすることもできます。
puts %[a\n\[\]b]      # \nで改行を表したり、囲み文字を\[や\]でエスケープできる
a
[]b
  • 以下の場合にエスケープなしで記号を使えます(式展開#{}をうっかり作らないようにしましょう)。
# 開始/終了が異なるペアの囲み記号で、かつ正しく入れ子になる場合はエスケープなしでもよい
%[a[bracket]b]        #=> "a[bracket]b"

# 二重引用符とバックスラッシュを除く記号はエスケープなしで書ける
%[!\#$%&'()*,-./:;[<>]?@^_`{|}~\\\"]  #=> "!\#$%&'()*,-./:;[<>]?@^_`{|}~\\\""
  • 以下の場合はエスケープしないとエラーになります。
# 囲み記号が正しく入れ子になっていない場合
%[abracket]b]  #=> syntax error, unexpected tIDENTIFIER, expecting end-of-input

# 開始/終了が同じ記号の場合
%!abracket!b!  #=> syntax error, unexpected tFID, expecting end

%qによる一重引用符文字列リテラルの代替

表記 意味 式展開 その他
%q[ ] 一重引用符' 'で囲むのと同等 できない \でエスケープや特殊文字を表現できない
\は単なる文字扱い)
  • 式展開は行われません(一重引用符と機能が同等なので)。
    • なお、IRBなどでは、%qと一重引用符のどちらも二重引用符として出力されます。
%q[Time: #{Time.new}]                 #=> "Time: \#{Time.new}"  -- 式展開されない
  • \が無視されますので、\nなどもそのまま出力されます。
puts %q[a\nb]      # \nで改行にならずそのまま出力される
a\nb

その他の%記法

上の2つを押さえておけば後はさほどではないと思いますので、残りは簡潔にとどめます。

表記 意味 式展開 その他
%r[ ] 正規表現リテラル //と同等 できる \でエスケープや特殊文字を表現できる
・末尾にフラグを追加できる
%i[ ] スペース区切りの文字列をシンボルの配列にする できない ・Ruby 2.0以降
%I[ ] スペース区切りの文字列をシンボルの配列にする できる \でエスケープや特殊文字を表現できる
・Ruby 2.0以降
%w[ ] スペース区切りの文字列を二重引用符" "で囲まれた語の配列にする できない
%W[ ] スペース区切りの文字列を二重引用符" "で囲まれた語の配列にする できる \でエスケープや特殊文字を表現できる
%x[ ] シェルでコマンドを実行する(` `と同等) できる
(セキュリティに注意)
%s[ ] 文字列全体を1つのシンボルに変換する できない
# 正規表現(式展開/エスケープ記法あり)
%r[課長代理補佐|課長代理|#{Time.now}]  #=> /課長代理補佐|課長代理|2019-02-27 18:15:11 +0900/

# シンボルの配列
%i[yamboo marboo tenkiyohoo]        #=> [:yamboo, :marboo, :tenkiyohoo]

# シンボルの配列(式展開/エスケープ記法あり)
%I[yamboo marboo #{Time.now}]       #=> [:yamboo, :marboo, :"2019-02-27 18:19:44 +0900"]

# 文字列の配列
%w[yamboo marboo tenkiyohoo]        #=> ["yamboo", "marboo", "tenkiyohoo"]

# 文字列の配列(式展開/エスケープ記法あり)
%W[yamboo marboo #{Time.now}]       #=> ["yamboo", "marboo", "2019-02-27 18:21:02 +0900"]

# シェルコマンドの実行
%x[uname]                           #=> "Darwin\n"

# シェルコマンドを式展開で実行(危険な例)
userinput = "rm -rf /"
%x[#{userinput}]                    #=> 😇

# 文字列全体をシンボルにする
%s[yamboo marboo tenkiyohoo]        #=> :"yamboo marboo tenkiyohoo"

3. 出力フォーマット用の%

ある意味最もややこしいのが、出力フォーマットで使われる%記号です。

まずは基本形から。

"i = %d" % 10      #=> "i = 10"
"i = %d" % -10     #=> "i = -10"

二重引用符に囲まれた部分"i = %d"%dは、渡された数値を十進数で出力するという書式指示になります。

以下のように小数値10.0を渡しても、出力は10になります。

"i = %d" % 10.0    #=> "i = 10"

String#%メソッド

上の式には%が「もうひとつある」ことにお気づきでしょうか。これは実はString#%というメソッドです。String#%は書式を設定して\dなどに渡します。標準ライブラリのメソッドなので、Rubyの構文ではありません。

この二重引用符で" "で囲まれた文字列では、この%メソッドがないとエラーになります。カンマを置いてもだめです。

"i = %d" 10.0       #=> syntax error, unexpected tFLOAT, expecting end-of-input
"i = %d", 10.0      #=> syntax error, unexpected ',', expecting end-of-input

メソッドである証拠に、以下のように"文字列".%と書いても動作は変わりません。つまり"文字列"はメソッドのレシーバーということになります。

"i = %d".% 10.0

実を言うと、この2番目の%が何なのかわからなかったのでこの記事を書いたのでした。

ただし、printfメソッドやsprintfメソッドの引数の形になっていれば、以下のように%メソッドとカンマ,区切りのどちらでも書けます。

printf("i = %d\n", 10)      #=> i = 10
printf("i = %d\n" % 10)     #=> i = 10

sprintf("i = %d", 10)       #=> "i = 10"
sprintf("i = %d" % 10)      #=> "i = 10"

%を使った書式指定について詳しくは以下の公式ドキュメントを参照いただければと思います。

参考: instance method String#% (Ruby 2.6.0)

String#%メソッドへの3種類の引数渡し方法

ここでは、String#%メソッドへの引数渡しについてまとめます。同ドキュメントの末尾で「利用頻度が低いので最後に説明します」と記載されていますが、そこそこ利用されているように思えます。

  1. "%": 引数(配列)の順序で指定
  2. "%<名前>フォーマット指定": 引数(ハッシュ)のキーで指定(フォーマットあり)
  3. "%{名前}": 引数(ハッシュ)のキーで指定(フォーマットなし)

1. 引数(配列)の順序で指定

先ほどの%d" % 10.0の引数10.0は、実際には配列として扱われます。つまり、レシーバーの文字列リテラルで%フォーマットが複数ある場合は、以下のように明示的に[ ]などで配列リテラルにすることで渡せます。なお、Rubyでは引数を囲む丸かっこを省略できます。

  • レシーバー(文字列リテラル): "%フォーマット"
  • String#%メソッド
  • 引数: 配列 [ ]
"%d %f" % [1, 2]   #=> "1 2.000000"
"%d %f" % ([1, 2]) #=> "1 2.000000"
  • もちろん引数が1つの場合も配列の形にできます。引数が1つであれば[]は省略できます。
"%d"    % [1]      #=> "1"
"%d"    % ([1])    #=> "1"
"%d"    % 1        #=> "1"
  • 配列リテラルの要素が複数の場合は[ ]省略できません。囲まないとカンマ区切りを正しく処理できません。丸かっこ()で囲むだけではダメです。
# 以下は不可
"%d %f" % 1, 2    #=> unexpected ',', expecting end-of-input
"%d %f" % (1, 2)  #=> SyntaxError: unexpected ',', expecting ')'

もちろん、配列を変数に入れて渡すのはOKです。

ary = [1, 2]
"%d %f" % ary     #=> "1 2.000000"

2. 引数(ハッシュ)で名前付き引数的に指定

以下のように、ハッシュの形式で引数を渡すことで、名前付き引数的に値を渡せます。%<名前>という<>を使った書式であり、直後にprintf系のフォーマット指定文字を追加できます。

  • レシーバー(文字列リテラル): "%<名前>フォーマット"
  • String#%メソッド
  • 引数: ハッシュ { }

具体的には以下のように使います。String#%メソッドは省略できません

"%<minute>d %<second>f" % { minute:1, second:2 }  #=> "1 2.000000"
  • ハッシュリテラルの{ }省略できません。ハッシュが1つであっても必要です。もちろんブロックではないのでdoendには置き換えられません。
"%<minute>d" % minute: 1  #=> SyntaxError: unexpected ':', expecting end-of-input
  • これも、ハッシュを変数に入れて渡すのはOKです。
hash = { minute:1, second:2 }
"%<minute>d %<second>f" % hash   #=> "1 2.000000"
  • フォーマット指定文字は省略できません。省略してもエラーにはなりませんが、値は出力されず、%だけが生で出力されてしまいます。
"%<minute>" % { minute: 1 }  #=> "%"

3. 引数(ハッシュ)で名前付き引数的に指定(フォーマット指定なし)

%{名前}という{ }を使った書式でも名前付き引数的にハッシュで値を渡せます。こちらの場合、printf系のフォーマット指定文字は使えません

  • レシーバー(文字列リテラル): "%{名前}"
  • String#%メソッド
  • 引数: ハッシュ { }
"%{minute} %{second}" % { minute: 1, second: 2.0 }  #=> "1 2.0"

もっともここまでくると、以下のように普通に式展開を使う方がよい気もしますが、ハッシュで渡せるのはメリットかもしれませんね。

minute = 1
second = 2.0
"#{minute} #{second}"  #=> "1 2.0"

参考: printfsprintfの書式はC言語由来

上に登場したprintfsprintfの書式指定は、どうもRubyっぽくありません。

その理由は、これらのメソッドの書式はC言語の同名の関数を踏襲しているからです。正確には、Rubyでprintfメソッドやsprintfを呼び出すとC言語の機能が呼び出されるのですが、完全な丸投げではなく、sprintfについてはC言語と若干の違いがあるとのことです。

Rubyのsprintfフォーマットは基本的に C 言語のsprintf(3)のものと同じです。ただし、short や long などのC特有の型に対する修飾子がないこと、2進数の指示子(%b, %B)が存在すること、sprintfのすべての方言をサポートしていないこと(%': 3桁区切り)などの違いがあります。
Ruby には整数の大きさに上限がないので、%b, %B, %o, %x, %Xに負の数を与えると (左側に無限に1が続くとみなせるので) ..f のような表示をします。絶対値に符号を付けた形式で出力するためには%+x% xのように指定します。
ruby-lang.orgより

Ruby公式ドキュメントから、printfのサンプルを引用します。非常に生々しいですね。

# ruby-lang.orgより
printf("%d %04x", 123, 123)               #=> "123 007b"
printf("%08b '%4s'", 123, 123)            #=> "01111011 ' 123'"
printf("%1$*2$s %2$d %1$s", "hello", 8)   #=> "   hello 8 hello"
printf("%1$*2$s %2$d", "hello", -8)       #=> "hello    -8"
printf("%+g:% g:%-g", 1.23, 1.23, 1.23)   #=> "+1.23: 1.23:1.23"
printf("%u", -123)                        #=> "..4294967173"

printfsprintf固有の%記号の使われ方については長くなりすぎるので本記事では触れず、以下のリンクにとどめることにします。

参考: module function Kernel.#printf (Ruby 2.6.0)
参考: sprintf フォーマット (Ruby 2.6.0)

関連記事

Rubyでの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由


Viewing all articles
Browse latest Browse all 1765

Trending Articles