CHAPTER SIXTEEN
Regular Expressions
Regular expressions provide you with powerful ways to find and modify pat-terns in text – not only short bits of text such as might be entered at a command prompt but also in huge stores of text such as might be found in files on disk. A regular expression takes the form of a pattern which is compared with a string. Regular expressions also provide means by which you can modify strings so that, for example, you might change specific characters by putting them into uppercase; or you might replace every occurrence of ‚Diamond‛ with ‚Ruby‛; or read in a file of programming code, extract all the comments and write out a new documentation file containing all the comments but none of the code. We’ll find out how to write a comment-extraction tool shortly. First, though, let’s take a look at some very simple regular expressions.
MAKING MATCHES
Just about the simplest regular expression would be a sequence of characters, such as ‘abc’, which you want to find in a string. A regular expression to match ‘abc’ can be created simply by placing those letters between two forward-slash delimiters /abc/. You can test for a match using the =~ operator method like this: regex0.rb puts( /abc/ =~ 'abc' ) #=> returns 0 If a match is made, an integer representing the character position in the string is returned. If no match is made, nil is returned.
THE BOOK OF RUBY
282
puts( /abc/ =~ 'xyzabcxyzabc' ) #=> returns 3 puts( /abc/ =~ 'xycab' ) #=> returns nil
You can also specify a group of characters, between square brackets, in which case a match will be made with any one of those characters in the string. Here, for example, the first match is made with ‘c’ and that character’s position in the string is returned: puts( /[abc]/ =~ 'xycba' ) #=> returns 2
While I’ve used forward slash delimiters in the examples above, there are alter-native ways of defining regular expressions: you can specifically create a new Regexp object initialized with a string or you can precede the regular expression with %r and use custom delimiters - non alphanumeric characters - as you can with strings (see Chapter 3). In the example below, I use curly brace delimiters: regex1.rb regex1 = Regexp.new('^[a-z]*$') regex2 = /^[a-z]*$/ regex3 = %r{^[a-z]*$} Each of the above, define a regular expression that matches an all-lowercase string (I’ll explain the details of the expressions shortly). These expressions can be used to test strings like this: def test( aStr, aRegEx ) if aRegEx =~ aStr then puts( "All lower case" ) else puts( "Not all lower case" ) end end test( "hello", regex1 ) #=> matches: "All lower case" test( "hello", regex2 ) #=> matches: "All lower case" test( "Hello", regex3 ) #=> no match: "Not all lower case"
CHAPTER SIXTEEN
283
To test for a match you can use if and the =~ operator: if_test.rb if /def/ =~ 'abcdef' The above expression evaluates to true if a match is made (and an integer is returned); it would evaluate to false if no match were made (and nil were re-turned): 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
Frequently, it is useful to attempt to match some expression from the very start of a string; the character ^ followed by a match term is used to specify this. It may also be useful to make a match from the end of the string; the character $ preceded by a match term is used to specify that. start_end1.rb puts( /^a/ =~ 'abc' ) #=> returns 0 puts( /^b/ =~ 'abc' ) #=> returns nil puts( /c$/ =~ 'abc' ) #=> returns 2 puts( /b$/ =~ 'abc' ) #=> returns nil
THE BOOK OF RUBY
284
Matching from the start or end of a string becomes more useful when it forms a part of a more complex pattern. Often such a pattern tries to match zero or more instances of a specified pattern. The * character is used to indicate zero or more matches of the pattern which it follows. Formally, this is known as a ‘quantifier’. Consider this example: start_end2.rb puts( /^[a-z 0-9]*$/ =~ 'well hello 123' ) Here, the regular expression specifies a range of characters between square brackets. This range includes all lowercase characters, a-z, all digits, 0-9, plus the space character (that’s the space between the ‘z’ and the ‘0’ in this expression). The ^ character means that the match must be made from the start of the string, the * after the range means that zero or more matches with the characters in the range must be made and the $ character means that the matches must be made right up to the end of the string. In other words, this pattern will only match a string containing lowercase characters, digits and spaces from the start right to the end of the string: puts( /^[a-z 0-9]*$/ =~ 'well hello 123' ) # match at 0 puts( /^[a-z 0-9]*$/ =~ 'Well hello 123' ) # no match due to ^ and uppercase 'W' Actually, this pattern will also match an empty string, since * indicates that zero or more matches are acceptable: puts( /^[a-z 0-9]*$/ =~ '' ) # this matches!
If you want to exclude empty strings, use + (to match one or more occurrences of the pattern): puts( /^[a-z 0-9]+$/ =~ '' ) # no match Try out the code in start_end2.rb for more examples of ways in which ^, $, * and + may be combined with ranges to create a variety of different match-patterns. You could use these techniques to determine specific characteristics of strings, such as whether or not a given string is uppercase, lowercase or mixed case:
CHAPTER SIXTEEN
285
regex2.rb aStr = "HELLO WORLD" case aStr when /^[a-z 0-9]*$/ puts( "Lower case" ) when /^[A-Z 0-9]*$/ puts( "Upper case" ) else puts( "Mixed case
" ) end Often regular expressions are used to process the text in a file on disk. Let’s suppose, for example, that you want to display all the full-line comments in a Ruby file but omit all the code or partial line-comments. You could do this by trying to match from the start of each line (^) zero or more whitespace characters (a whitespace character is represented by s) up to a comment character (‘#’): regex3a.rb # displays all the full-line comments in a Ruby file File.foreach( 'regex1.rb' ){ |line| if line =~ /^s*#/ then puts( line ) end }
MATCH GROUPS
You can also use a regular expression to match one or more substrings. In order to do this, you should put part of the regular expression between round brackets. Here I have two groups (sometimes called ‘captures’), the first tries to match the string ‘hi’, the second tries to match a string starting with ‘h’, followed by any three characters (a dot means ‘match any single character’ so the three dots here will match any three consecutive characters) and ending with ‘o’:
THE BOOK OF RUBY
286
groups.rb /(hi).*(h...o)/ =~ "The word 'hi' is short for 'hello'." After evaluating groups in a regular expression, a number of variables, equal to the number of groups, will be assigned the matched value of those groups. These variables take the form of a $ followed by a number: $1, $2, $3 and so on. After executing the above code, I can access the variables, $1 and $2 like this: print( $1, " ", $2, "
" ) #=> displays: hi hello Note that if the entire regular expression is unmatched, none of the group variables with be initialized. If, for example, ‘hi’ is in the string but ‘hello’ isn’t, neither of the group variables will be initialized. Both would be nil. Here is another example, which returns three groups, each of which contains a single character (given by the dot). Groups $1 and $3 are then displayed: /(.)(.)(.)/ =~ "abcdef" print( $1, " ", $3, "
" ) #=> displays: a c Here is a new version of the comment-matching program which was given earlier (regex3a.rb); this has now been adapted to use the value of the group (*.) which returns all the characters (zero or more) following the string matched by the preceding part of the regular expression (which here is: ^s*#). This matches zero or more whitespace (s*) characters from the start of the current line (^) up to the first occurrence of a hash or pound character: #: regex3b.rb File.foreach( 'regex1.rb' ){ |line| if line =~ /^s*#(.*)/ then puts( $1 ) end } The end result of this is that only lines in which the first printable character is # are matched; and $1 prints out the text of those lines minus the # character itself. As we shall see shortly, this simple technique provides the basis for a useful tool for extracting documentation from a Ruby file.
CHAPTER SIXTEEN
287
You aren’t limited merely to extracting and displaying characters verbatim; you can also modify text. This example, displays the text from a Ruby file but changes all Ruby line-comment characters (‘#’) preceding full-line comments to C-style line-comments (‘//’): regex4.rb File.foreach( 'regex1.rb' ){ |line| line = line.sub(/(^s*)#(.*)/, '1//2') puts( line ) } In this example, the sub method of the String class has been used; this takes a regular expression as its first argument (here /(^s*)#(.*)/) and a replacement string as the second argument (here '1//2') . The replacement string may contain numbered place-holders such as 1 and 2 to match any groups in the regular expression - here there are two groups between round brackets: (^s*) and (.*). The sub method returns a new string in which the matches made by the regular expression are substituted into the replacement string, while any un-matched elements (here the # character, are omitted).
MATCHDATA
The =~ ‘operator’ is not the only means of finding a match. The Regexp class also has a match method. This works in similar way to =~ but, when a match is made, it returns a MatchData object rather than an integer. A MatchData object contains the result of a pattern match. At first sight, this may appear to be a string< match.rb puts( /cde/ =~ 'abcdefg' ) #=> 2 puts( /cde/.match('abcdefg') ) #=> cde In fact, it is an instance of the MatchData cla
บทที่หกนิพจน์ทั่วไปนิพจน์ทั่วไปให้วิธีที่มีประสิทธิภาพการค้นหา และแก้ไข terns พัฒน์ในข้อความ – ไม่เพียงสั้น ๆ ข้อความเช่นอาจเป็นที่พร้อมท์รับคำสั่ง แต่ในร้านค้าขนาดใหญ่ของข้อความเช่นอาจจะพบในแฟ้มบนดิสก์ นิพจน์ทั่วไปใช้แบบของรูปแบบซึ่งเมื่อเทียบกับสาย นิพจน์ทั่วไปยังมีวิธีที่คุณสามารถแก้ไขสายอักขระเพื่อให้ เช่น คุณอาจเปลี่ยนอักขระเฉพาะ โดยใส่ลงในพิมพ์ใหญ่ หรือคุณอาจแทนทุกเหตุการณ์ของ 'เพชร' กับ 'ทับทิม' หรืออ่านไฟล์ของรหัสโปรแกรม แยกข้อคิดเห็นทั้งหมด และเขียนออกแฟ้มเอกสารใหม่ที่ประกอบด้วยข้อคิดเห็นทั้งหมดแต่ไม่มีรหัส เราจะพบว่าวิธีการเขียนเครื่องมือสกัดคิดช้า แรก แม้ว่า ลองมาดูที่บางนิพจน์ทั่วไปง่ายมากทำให้ตรงกันเพียงเกี่ยวกับง่ายนิพจน์ทั่วไปจะเป็นลำดับของตัวอักษร เช่น "abc" ที่คุณต้องการค้นหาสายอักขระ สามารถสร้างนิพจน์ทั่วไปเพื่อให้ตรงกับ 'abc' เพียงแค่ โดยการทำตัวอักษรเหล่านั้นระหว่างสองทับคั่น abc / คุณสามารถทดสอบการใช้ =การแข่งขัน ~ วิธีดำเนินการดังนี้: ทำให้ regex0.rb (/abc/ = ~ 'abc') #= > ส่งกลับค่า 0 ถ้าการแข่งขันถูก เป็นจำนวนเต็มที่แสดงตำแหน่งของอักขระในสายอักขระถูกส่งกลับได้ ถ้าจับไม่ถูก nil นั่นจะถูกส่งกลับหนังสือของทับทิม282ทำให้ (/abc/ = ~ 'xyzabcxyzabc') #= > กลับทำให้ 3 (/abc/ = ~ 'xycab') #= > กลับ nilนอกจากนี้คุณยังสามารถระบุกลุ่มอักขระ ระหว่างก้าม จะทำกรณีตรงกับใด ๆ ของอักขระในสายอักขระ ที่นี่ เช่น ทำการจับคู่ครั้งแรกกับ 'c' และตำแหน่งของอักขระในสายอักขระถูกส่งกลับ: ทำให้ (/ [abc] / = ~ 'xycba') #= > ส่งกลับ 2ในขณะที่ฉันใช้ทับคั่นในตัวอย่างข้างต้น มีพื้นเปลี่ยนวิธีกำหนดนิพจน์ทั่วไป: คุณสามารถสร้างวัตถุ Regexp ใหม่เริ่มต้น ด้วยสายอักขระเฉพาะ หรือคุณสามารถนำหน้านิพจน์ปกติกับ %r และใช้ตัวคั่นที่กำหนดเอง -ไม่ใช่อักษร - คุณสามารถ มีสายอักขระ (ดูบทที่ 3) ในตัวอย่างด้านล่าง ใช้ตัวคั่นปีก: regex1.rb regex1 = Regexp.new('^[a-z]*$') regex2 = / ^ [a-z] * $/ regex3 = %r { ^ [a-z] * $} แต่ละข้าง กำหนดนิพจน์ทั่วไปที่ตรงกับสตริงตัวพิมพ์เล็กทั้งหมด (ผมจะอธิบายรายละเอียดของนิพจน์ในไม่ช้า) นิพจน์เหล่านี้สามารถใช้เพื่อทดสอบสายนี้: def ทดสอบ (aStr, aRegEx) aRegEx = ~ aStr แล้วทำให้ ("ทุกกรณี") อื่นทำให้ ("ไม่เล็ก") สิ้นสุดการทดสอบ ("สวัสดี" regex1) #= > ตรงกับ: ทดสอบ "ทุกกรณี" ("สวัสดี" regex2) #= > ตรงกับ: ทดสอบ "ทุกกรณี" ("สวัสดี" regex3) #= > ไม่ตรงกัน: "ไม่เล็ก"บทที่หก283การทดสอบตรงกันคุณสามารถใช้และ = ~ ดำเนิน: if_test.rb ถ้า /def/ = ~ "abcdef" ข้างต้นนิพจน์เป็นจริงถ้าตรงกันจะทำได้ (และถูกส่งกลับเป็นเลขจำนวนเต็ม); มันจะประเมินเป็นเท็จถ้าจับคู่ไม่ได้ทำ (และ nil นั่นได้อีกเกลียด): RegEx /def/ Str1 = = 'abcdef' Str2 = 'ghijkl' ถ้า RegEx = ~ Str1 แล้วทำให้ทำให้อื่น ('จริง') ('เท็จ') จบ#= > แสดง: ถ้าจริง RegEx = ~ Str2 แล้วทำให้ทำให้อื่น ('จริง') ('เท็จ') จบ#= > แสดง: เท็จบ่อย ๆ เป็นประโยชน์พยายามให้ตรงกับนิพจน์บางพักสายอักขระ อักขระ ^ ตาม โดยตรง ใช้ระยะเพื่อระบุนี้ นอกจากนี้ยังอาจเป็นประโยชน์ทำให้การแข่งขันจากจุดสิ้นสุดของสตริ $ อักขระที่นำหน้า ด้วยคำที่ตรงกันระบุว่า ทำให้ start_end1.rb (/ ^ / = ~ 'abc') #= > กลับทำให้ 0 (/ ^ b / = ~ 'abc') #= > กลับทำให้ nil (/c$ / = ~ 'abc') #= > กลับทำให้ 2 (/b$ / = ~ 'abc') #= > กลับ nilหนังสือของทับทิม284จับคู่จากเริ่มต้นหรือจุดสิ้นสุดของสตริงที่จะมีประโยชน์มากเมื่อแบบเป็นส่วนหนึ่งของรูปแบบที่ซับซ้อนมากขึ้น มักจะเป็นรูปแบบพยายามให้ตรงกับศูนย์ หรือมากกว่ากรณีของรูปแบบที่ระบุ * ใช้อักขระเพื่อระบุตรงกับศูนย์ หรือมากกว่าของรูปแบบซึ่งเป็นไปตามนั้น อย่างเป็นกิจจะลักษณะ นี้จะเรียกว่า 'quantifier' พิจารณาตัวอย่างนี้: ทำให้ start_end2.rb (/ ^ [a-z 0-9] * $/ = ~ 'ดีสวัสดี 123') ที่นี่ นิพจน์ทั่วไประบุช่วงของอักขระระหว่างก้ามปู ช่วงนี้มีอักษรตัวพิมพ์เล็กทั้งหมด a-z ตัว เลขทั้งหมด 0-9 บวกช่อง (ที่ช่องว่างระหว่าง 'z' และ '0' ในนิพจน์นี้) ^ หมายความว่าต้องทำการจับคู่จากจุดเริ่มต้นของข้อความ อักขระ * หลังจากช่วงหมายความ ว่า ศูนย์ หรือมากกว่าตรงกับตัวละครในช่วงต้องทำ และอักขระ$หมายความ ว่า ต้องทำการแข่งขันจนถึงจุดสิ้นสุดของสตริการ ในคำอื่น ๆ รูปแบบนี้จะเฉพาะตรงกับสายอักขระประกอบด้วยอักขระตัวพิมพ์เล็ก ตัวเลข และช่องว่างจากขวาไปสิ้นสุดของสายอักขระ: ทำให้ (/ ^ [a-z 0-9] * $/ = ~ 'ดีสวัสดี 123') #ตรงที่ 0 ทำให้ (/ ^ [a-z 0-9] * $/ = ~ 'ดีสวัสดี 123') #ไม่ตรงเนื่อง ^ และใหญ่ 'W' จริง รูปแบบนี้จะยังตรงกับสตริงว่างเปล่า ตั้งแต่ * ระบุว่า ตรงกับศูนย์ หรือมากกว่าเป็นที่ยอมรับ: ทำให้ (/ ^ [a-z 0-9] * $/ = ~ '') #ตรงนี้ถ้าคุณต้อง การแยกสตริงที่ว่างเปล่า ใช้ + (ให้ตรงกับรายการหนึ่ง หรือหลายรูปแบบ): ทำให้ (/ ^ [a-z 0-9] + $/ = ~ '') #ลองไม่ตรงกับรหัสใน start_end2.rb ตัวอย่างเพิ่มเติมวิธีการ ^, $, * และ + อาจใช้ร่วมกับสร้างตรงรูปแบบที่หลากหลาย คุณสามารถใช้เทคนิคเหล่านี้เพื่อระบุลักษณะเฉพาะของสาย เช่นหรือไม่กำหนดสายอักขระเป็นตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก หรือผสมกรณี:บทที่หก285regex2.rb aStr = aStr "สวัสดี โลก" กรณีเมื่อ / ^ [a-z 0-9] * $/ ใส่ ("เล็ก") เมื่อ / ^ [A-Z 0-9] * $/ ท้าย ("ผสม case
") Often นิพจน์ทั่วไปที่ใช้ในการประมวลผลข้อความในแฟ้มบนดิสก์ทำให้ทำให้ ("เล็ก") อื่น ลองสมมติว่า เช่น ที่ คุณต้องการแสดงความเห็นเต็มบรรทัดทั้งหมดในแฟ้มทับทิม แต่ละรหัสทั้งหมดหรือบางส่วนบรรทัดข้อคิดเห็น คุณสามารถทำเช่นนี้ โดยพยายามจับคู่จากแต่ละบรรทัด (^) เป็นศูนย์ หรือมากกว่าช่องว่างอักขระ (ตัวอักษรช่องว่างจะถูกแสดง ด้วย s) เห็นอักขระ ('#'): แสดง# regex3a.rb ทั้งหมดเต็มบรรทัดข้อคิดเห็นในรูเป็นไฟล์ File.foreach ('regex1.rb') { |line| ถ้าบรรทัด = ~ / ^ s*#/ แล้วทำให้สิ้นสุด (บรรทัด) }กลุ่มจับคู่นอกจากนี้คุณยังสามารถใช้นิพจน์ทั่วไปเพื่อให้ตรงกับอย่าง น้อยหนึ่ง substrings ไม่ได้ คุณควรใส่ส่วนของนิพจน์ปกติระหว่างวงเล็บกลม ที่นี่มี 2 กลุ่ม (บางครั้งเรียกว่า 'จับ'), พยายามแรกให้ตรงกับสายอักขระ 'สวัสดี' ที่สองพยายามให้ตรงกับสายอักขระการเริ่มต้น ด้วย 'h' ตาม ด้วยอักขระใด ๆ สาม (จุดหมายความว่า 'ตรงกับอักขระเดี่ยว' เพื่อสามจุดนี่จะตรงกับอักขระใด ๆ ติดต่อกันสาม) และลงท้ายด้วย ' โอ:หนังสือของทับทิม286groups.rb /(hi).*(h...o)/ =~ "The word 'hi' is short for 'hello'." After evaluating groups in a regular expression, a number of variables, equal to the number of groups, will be assigned the matched value of those groups. These variables take the form of a $ followed by a number: $1, $2, $3 and so on. After executing the above code, I can access the variables, $1 and $2 like this: print( $1, " ", $2, "
" ) #=> displays: hi hello Note that if the entire regular expression is unmatched, none of the group variables with be initialized. If, for example, ‘hi’ is in the string but ‘hello’ isn’t, neither of the group variables will be initialized. Both would be nil. Here is another example, which returns three groups, each of which contains a single character (given by the dot). Groups $1 and $3 are then displayed: /(.)(.)(.)/ =~ "abcdef" print( $1, " ", $3, "
" ) #=> displays: a c Here is a new version of the comment-matching program which was given earlier (regex3a.rb); this has now been adapted to use the value of the group (*.) which returns all the characters (zero or more) following the string matched by the preceding part of the regular expression (which here is: ^s*#). This matches zero or more whitespace (s*) characters from the start of the current line (^) up to the first occurrence of a hash or pound character: #: regex3b.rb File.foreach( 'regex1.rb' ){ |line| if line =~ /^s*#(.*)/ then puts( $1 ) end } The end result of this is that only lines in which the first printable character is # are matched; and $1 prints out the text of those lines minus the # character itself. As we shall see shortly, this simple technique provides the basis for a useful tool for extracting documentation from a Ruby file.บทที่หก287ไม่ได้จำกัดเพียงการแยก และแสดงอักขระทุกตัวอักษร นอกจากนี้คุณยังสามารถปรับเปลี่ยนข้อความ อย่างนี้ แสดงข้อความจากแฟ้มทับทิม แต่เปลี่ยนอักขระทั้งหมดบรรทัดข้อคิดเห็นทับทิม ('#') ก่อนหน้าเต็มบรรทัดข้อคิดเห็นลักษณะ C บรรทัดข้อคิดเห็น ('/ /'): regex4.rb File.foreach ('regex1.rb') {บรรทัด |line| = line.sub(/(^s*)#(.*) /, '1//2') ทำให้ (บรรทัด) } ในตัวอย่างนี้ วิธีย่อยของคลาสตริงที่ใช้ นี้ใช้เป็นอาร์กิวเมนต์แรกของนิพจน์ทั่วไป (ที่นี่ /(^s*)#(.*)/) และการแทนที่สตริงที่เป็นอาร์กิวเมนต์สอง (ที่นี่ ' 1//2') สายอักขระการแทนที่อาจประกอบด้วยผู้ถือที่หมายเลข 1 และ 2 ให้ตรงกับกลุ่มใด ๆ ในนิพจน์ทั่วไป - ที่นี่มี 2 กลุ่มระหว่างวงเล็บกลม: (^ s*) และ (*) วิธีย่อยส่งกลับสายอักขระใหม่ที่ตรงกันโดยนิพจน์ทั่วไปจะแทนลงในสายอักขระการแทน ในขณะที่องค์ประกอบใดไม่ตรงกัน (ที่นี่#อักขระ จะถูกละเว้นจาก)MATCHDATA=การ ~ 'ผู้ประกอบการ' เป็นการไม่ค้นหาตรงกัน คลาส Regexp ยังมีวิธีการจับคู่ ใช้งานในลักษณะคล้ายการ = ~ แต่ เมื่อทำการแข่งขัน จะส่งกลับวัตถุ MatchData แทนที่เป็นจำนวนเต็ม วัตถุ MatchData ประกอบด้วยผลของการแข่งขันรูปแบบ ที่ตา นี้อาจจะเป็นสตริง < ใส่ match.rb (/cde/ = ~ "abcdefg") #= > 2 ทำให้ (/cde/.match('abcdefg')) #= > cde ในความเป็นจริง มันเป็นอินสแตนซ์ของ MatchData cla
การแปล กรุณารอสักครู่..