ruby(正規表現)
文字列(String)の置き換えや取得方法
①Regexp#matchでマッチした時に返ってきたMatchDataを変数名[ ]で取得する。
②String#scanで正規表現にマッチした部分を配列に入れて返す。
③String#[ ]で正規表現にマッチした部分を抜き出す。
④gsubで第1引数にマッチした文字列を第2引数の文字列で置き換える。
(どれも名前付きキャプチャを使える)
下記の記事も参考にした。
Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)|TechRacho by BPS株式会社
Macth
Regexp#match (Ruby 3.1 リファレンスマニュアル)
match(str, pos = 0) -> MatchData | nil
match(str, pos = 0) {|m| ... } -> object | nil
指定された文字列 str に対して位置 pos から自身が表す正規表現によるマッチングを行います。マッチした場合には結果を MatchData オブジェクトで返します。マッチしなかった場合 nil を返します。
irb(main):010:0> p bar = /foo(.*)baz/.match("foobarbaz").to_a[1]
"bar"
=> "bar"
MatchData オブジェクトを配列にして、1個目の値を取り出している。
下記のやり方も出来るが配列にして取り出した方が、一度に代入できる。
irb(main):003:0> bar = /foo(.*)baz/.match("foobarbaz")
=> #<MatchData "foobarbaz" 1:"bar">
irb(main):004:0> p bar
#<MatchData "foobarbaz" 1:"bar">
=> #<MatchData "foobarbaz" 1:"bar">
irb(main):005:0> p bar[1]
"bar"
=> "bar"
String#match
もあり、それはレシーバと引数が逆になっていて挙動は同じ。
String#match? (Ruby 3.1 リファレンスマニュアル)
match?(regexp, pos = 0) -> bool
"Ruby".match?(/R.../) #=> true
trueかfalseを返すもの
これも レシーバと引数が逆バージョンのRegexp#match?
がある。
string['text']
・string['text']で単純な文字列を検索するのであれば正規表現にしない。
String#[] (Ruby 3.1 リファレンスマニュアル)
self[substr] -> String | nil
slice(substr) -> String | nil
self が substr を含む場合、一致した文字列を新しく作って返します。 substr を含まなければ nil を返します。
irb(main):004:0> p a = "foobar"["bar"]
"bar"
=> "bar"
self[regexp, nth = 0] -> String
slice(regexp, nth = 0) -> String
正規表現 regexp の nth 番目の括弧にマッチする最初の部分文字列を返します。 nth を省略したときや 0 の場合は正規表現がマッチした部分文字列全体を返します。正規表現が self にマッチしなかった場合や nth に対応する括弧がないときは nil を返します。
p "def getcnt(line)"[ /def\s+(\w+)/, 1 ] # => "getcnt"
下記の様にすれば置き換えも出来る
str[/^Hello, (.+?)$/, 1] = 'MRI' # strのキャプチャグループ1を置き換える
puts str
#=> "Hello, MRI"
self[regexp, name] -> String
slice(regexp, name) -> String
正規表現 regexp の name で指定した名前付きキャプチャにマッチする最初の部分文字列を返します。正規表現が self にマッチしなかった場合は nil を返します。
s = "FooBar"
s[/(?<foo>[A-Z]..)(?<bar>[A-Z]..)/] # => "FooBar"
s[/(?<foo>[A-Z]..)(?<bar>[A-Z]..)/, "foo"] # => "Foo"
String#gsub
String#gsub (Ruby 3.1 リファレンスマニュアル)
gsub(pattern, replace) -> String
文字列中で pattern にマッチする部分全てを文字列 replace で置き換えた文字列を生成して返します。
p 'xxbbxbb'.gsub(/x+(b+)/, 'X<<\1>>') # => "X<<bb>>X<<bb>>"
置換文字列 replace 中の & と \0 はマッチした部分文字列に、 \1 ... \9 は n 番目の括弧の内容に置き換えられます。
p 'xbbb-xbbb'.gsub(/x(b+)/, "\\1") # => "bbb-bbb" # OK
p 'xbbb-xbbb'.gsub(/x(b+)/, '\1') # => "bbb-bbb" # OK
p 'xbbb-xbbb'.gsub(/x(b+)/, '\\1') # => "bbb-bbb" # OK
$1に値が入るのはこのメソッドの処理が終了してから。
また、「\」が部分文字列との置き換えという特別な意味を持つため、 replace に「\」自身を入れたいときは「\」を二重にエスケープしなければない。
このような間違いを確実に防止し、コードの可読性を上げるには、 & や \1 よりも下記のようにブロック付き形式の gsub を使うべきです。
p 'xbbb-xbbb'.gsub(/x(b+)/) { $1 } # => "bbb-bbb" # OK
puts '\n'.gsub(/\\/) { '\\\\' } # => \\n # OK
とリファレンスにはある。
Regexp.last_match(1)を優先する」について
rubocopでの警告「$1よりリファレンスにはあるけど、 { $1 } という書き方をするとrubpcopの警告が出る。
最後にマッチしたグループの取り出しにPerl由来の$記法($1や$2など)を使わずにRegexp.last_match(n) を使う様にとのルールがある様。
ちなみにキャプチャグループは番号での指定ではなく名前付きグループでの指定が望ましいとのルールもある様。
Regexp.last_match(1)とは
Regexp.last_match (Ruby 3.1 リファレンスマニュアル)
カレントスコープで最後に行った正規表現マッチの MatchData オブジェクトを返します。このメソッドの呼び出しは $~ の参照と同じです
修正前のコード
# frozen_string_literal: true
require 'optparse'
class LSCommand
def initialize
opt = OptionParser.new
params = {}
opt.on('-a') { |v| params[:a] = v }
opt.on('-r') { |v| params[:r] = v }
opt.on('-l') { |v| params[:l] = v }
opt.parse!(ARGV)
@path = Dir.pwd if ARGV #== []
@path = ARGV if ARGV[1]
@path = ARGV[0] if ARGV[0] && ARGV[1].nil?
end
def output
case @path
when String
file_date_in(@path)
output_without_options
when Array
@path.each do |path|
puts "#{path}:"
file_date_in(path)
output_without_options
end
end
end
def file_date_in(path)
@file_date = {}
@max_name_size = 0
Dir.chdir(path) do
Dir.glob('*').each do |n|
filename = n.gsub(%r{.+/([^/]+$)}) { '\1' }
@max_name_size = filename.size if @max_name_size < filename.size
@file_date[:"#{filename}"] = File.stat(filename)
end
end
end
def output_without_options
case @max_name_size
when (35..)
row = 1
when (24..34)
row = 2
width = 36
else
row = 3
width = 23
end
line = @file_date.size / row
line += 1 if (@file_date.size % row).positive?
line.times do |time|
@file_date.select.with_index { |(_k, _v), i| i % line == time }.each_key { |k| print format("%-#{width}s", k) }
print "\n"
time += 1
end
print "\n"
end
end
ls = LSCommand.new
ls.output
Rubocopの言っているのに従うとこうなりそうだが、このコードは読みづらい。
filename = "/Users/desuktop".gsub(%r{.+/([^/]+$)}) { Regexp.last_match(1) }
p filename #=> “desuktop"
そもそもstring['text’]の書き方が一番分かりやすく書けそう。
filename = %r{.+/([^/]+$)}.match("/Users/desuktop").to_a[1]
と思ったが、このコードが動いていなかったことが発覚した。
Dir.glob(‘*’)で返ってくる値は絶対パスだと思っていたら、ファイル名だけだったので、置き換える必要がなくなった😵確認不足。コードも動いてしまっていたので気づかなかった。。
送ってしまっていたプルリクエストを修正した。
その他
-
pattern にマッチした最初の部分だけ置き換えるにはString#subが使える。
String#sub (Ruby 3.1 リファレンスマニュアル) -
文字列を置き換えるだけならString#trが使える。
String#tr (Ruby 3.1 リファレンスマニュアル) -
文字列を取り除くなら置き換え後を''にすると良さそう。
'test.rb'.tr('.rb', '') #=> "test"
'test.rb'.gsub('.rb', '') # => "test"
Rubyの書き方の参考になるもの
【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ|TechRacho by BPS株式会社
本スタイルガイドの元になっているbbatsov/ruby-style-guideは、同じ著者によるRuboCop gemで使われているスタイルです。
とのことなので参考になる。
・パターンの冒頭と末尾は^や$ではなく、\Aと\zで表すこと
・複雑な置換では、#subや#gsubにブロックやハッシュを与えてもよい
・%リテラルについて
など他にも参考になることがあった。