1.4.1.16. fejezet, Reguláris kifejezések

Mintát készíthetünk két jobbra dőlő perjel között (pl.: //). A minta illeszkedésének tesztelését sokféle kép végezhetjük. Most a =~ operátorral próbáljuk ki, pl.:

puts( /abc/ =~ 'abc' ) #=> returns 0

A minta illesztés sikerült, egy számot kapunk vissza, hogy a string-en belül hol talált rá a tesztelés. Ha nincs találat, nil értékkel tér vissza. A következő minta kétszer is előfordul, de meg egyenlőre csak az elsőt találtuk meg:

puts( /abc/ =~ 'xyzabcxyzabc' ) #=> returns 3

Ezek mind reguláris kifejezések:

regex1 = Regexp.new('^[a-z]*$')
regex2 = /^[a-z]*$/
regex3 = %r{^[a-z]*$}

a %r-nél külön jelölést használtunk, - ami nem alfanumerikus karakter -, lásd a 3. fejezetet.

RegEx = /def/ 
Str1 = 'abcdef' 
Str2 = 'ghijkl' 
 
if RegEx =~ Str1 then
  puts( 'true' ) 
else
  puts( 'false' )
end #=> displays: true
 
if RegEx =~ Str2 then
  puts( 'true' ) 
else
  puts( 'false' ) 
end #=> displays: false

A fenti tesztelés már több if..then..else elemet is tartalmaz. Fájlokon is elvégezhetjük a minta illesztést:

File.foreach( 'regex1.rb' ){ |line|	
  if line =~ /^\s*#/ then
    puts( line )
  end
}

Zárójelekkel csoportokat alakíthatunk ki a mintába.

/(hi).*(h...o)/ =~ "The word 'hi' is short for 'hello'."

Csoportosítás eredményeként a $ kezdetű változók kapnak értéket, úgy mint $1 és $2. Ha csak az egyik csoport is hiányzik, nil értéket ad vissza a kifejezés, és a globális változók nem kapnak értéket.

A következő minta a # megjegyzés sorokat C stílusú // formára alakítja

File.foreach( 'regex1.rb' ){ |line|
  line = line.sub(/(^\s*)#(.*)/, '\1//\2')
  puts( line ) 
}

Itt a sub metódus második paraméterében a \1 és \2 a megtalált két karaktercsoportot jelenti.

Adat megtalálás

A =~ nem az egyetlen módja a minta megtalálásának.

puts( /cde/ =~ 'abcdefg' ) #=> 2 
puts( /cde/.match('abcdefg') ) #=> cde

Valójában a második egy MatchData osztály, ami tartalmaz egy stringet. A találati lista tömbön végig is lépegethetünk a capture vagy a to_a metódussal:

x = /(^.*)(#)(.*)/.match( 'def myMethod # This is a very nice method' )
 
puts( '---x.captures---' )
p( x.captures )
x.captures.each{ |item| puts( item ) }
 
puts( "\n---x.to_a---" )
p( x.to_a )
x.to_a.each{ |item| puts( item ) }

Eredmények:

---x.captures---
["def myMethod ", "#", " This is a very nice method"]
def myMethod 
#
 This is a very nice method
 
---x.to_a---
["def myMethod # This is a very nice method", "def myMethod ", "#", " This is a very nice method"]
def myMethod # This is a very nice method
def myMethod 
#
 This is a very nice method

Apró különbség, hogy a to_a metódus a 0. indexében az egész stringet tartalmazza.

Van két metódus még, ami a találat előtti és a találat utáni stringet adja vissza:

x = /#/.match( 'def myMethod # This is a very nice method' ) 
puts( x.pre_match ) #=> def myMethod
puts( x.post_match ) #=> This is a very nice method

Alternatívaként használhatjuk a $ változót a backquote ($`) és normal quote ($') mellett. Előbbi a találat előttit, utóbbi az utána lévő stringet adja vissza.

Ha a match metódust csoportokkal használjuk, tömb stílusú indexelést használhatunk a MatchData objektumra. A nulladik indexen az eredeti szöveg található.

puts( /(.)(.)(.)/.match("abc")[2] ) #=> "b"

Egy speciális változó, a $~ tartalmazza az utoljára keletkezett MatchData objektumot. És el ne feledjük, hogy használhatunk tömb indexelést itt is:

puts( $~[0], $~[1], $~[3] )

Habár a tömb metódusainak egész tárházát használhatjuk, van olyan metódus, amit csak a to_a és captures metódusokon keresztül érünk el.

puts( $~.captures.sort )

Falánk mintaillesztés

Ha a keresés nem áll meg az első megtalált illeszkedésnél, akkor beszélünk falánk mintaillesztésről. Ez a viselkedés a * vagy + jelek után tett ?-el megváltoztatható:

puts( /.*at/.match('The cat sat on the mat!') ) #=> returns: The cat sat on the mat
puts( /.*?at/.match('The cat sat on the mat!') ) #=> returns: The cat
 
puts( /.+\\/.match('C:\mydirectory\myfolder\myfile.txt') ) #=> C:\mydirectory\myfolder\ 
puts( /.+?\\/.match('C:\mydirectory\myfolder\myfile.txt') ) #=> C:\

String metódusok

A =~ ,match és a sub metódusok mellett ott van még a scan, ami végiglépked a stringen, keresve az összes illeszkedő találatot.

TESTSTR = "abc is not cba"
puts( "\n--match--" ) 
b = /[abc]/.match( TESTSTR ) #=> MatchData: "a" 
puts( "--scan--" ) 
a = TESTSTR.scan(/[abc]/) #=> Array: ["a", "b", "c", "c", "b", "a"]

A scan eredménye átadható egy blokknak:

a = TESTSTR.scan(/[abc]/){|c|
  print( c.upcase ) 
} #=> ABCCBA

Egyik változata a String.slice metódusnak paraméterül kap egy kifejezést, és az illeszkedő rész stringeket adja vissza. Ha String.slice! metódust (vegyük észre a ! jelet) hajtunk végre, a talált rész stringeket kiveszi az eredeti szövegből, és az így előállt stringet adja vissza.

s = "def myMethod # a comment meteroid"
 
puts( s.slice( /m.*?d/ ) )
puts( s )
puts( s.slice!( /m.*d/ ) )
puts( s )

Eredmények:

myMethod
def myMethod # a comment meteroid
myMethod # a comment meteroid
def 

A split metódussal tömb elemekre vágható az adott kifejezések mentén.

s = "def myMethod # a comment"
p( s.split( /\s/ ) ) #=> ["def", "myMethod", "#", "a", "comment"]

A sub metódussal egy mintára kereshetünk, és kicserélhetjük az első találatot egy stringre, mint azt korábban láthattuk. A gsub és a gsub()! pedig az összes illeszkedő részt kicseréli a második paraméterében kapott stringre. A sub!() metódus pedig ugyanazt teszi, mint a sub, csak megváltoztatja a hatáskört arra a stringre, amielyikből hívták.

Fájl műveletek

Most nézzük meg, hogyan számlálja a szavakat a Ruby reguláris kifejezéssel.

count = 0
File.foreach( 'regex1.rb' ){ |line|	
  count += line.scan( /[a-z0-9A-Z]+/ ).size
}
puts( "There are #{count} words in this file." )

És hogy hogyan veszi ki a megjegyzés jeleket a fájlból:

File.open( 'testfile1.txt', 'w' ){ |f|
  File.foreach( 'regex1.rb' ){ |line|
    f.puts( line.sub(/(^\s*)#(.*)/, '\1//\2') ) 
  }
}

A Ruby reguláris kifejezései tesztelhetők a http://rubular.com weblapon. Itt található egy kis táblázat is a kifejezések jelentéséről.