设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 1676|回复: 0
打印 上一主题 下一主题

[通用发布] 正则表达式之准新手脚本教学(略浅)(2+3)看3请使用滑轮

[复制链接]

Lv2.观梦者

梦石
0
星屑
508
在线时间
1478 小时
注册时间
2011-9-17
帖子
1316

开拓者贵宾

跳转到指定楼层
1
发表于 2011-10-24 14:26:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 iisnow 于 2011-10-27 13:18 编辑

什么情况啊  “$~” 这个式子不能写嘛?,帖子怎么老异常啊
哎,上一节索引:
正则表达式之准新手脚本教学(略浅)(1)
我果然还是很闲啊,今天就接着2吧

接上一次讲都字符簇,今天再提几点:
1.字符簇归根结底只能匹配一个字符
/r[a\so]m/  #就是说它能匹配  "ram"、"r m"、"rom"  三种字符段
再复杂也只能匹配一个!集合中的一个字符

2.字符簇中的范围a-p,4-9,,等等均不能倒过来写 = =
即不能为p-a,6-3(RUBY没有那么智能),另外大小写不能混用(大家不要看着我全部是小写就忘记大写了= =)
虽然在范围类里面"A".."b"指ASCII码在A与b之间的字符,不仅有字母,还有一些其他字符,故建议不要采用
(这句话刚改,前面说错了)
对于空集而言,字符簇是支持的:
/[A-N&&Q-VY]/  就是空集,不匹配任何字符(这pattern就没意义了= =)
空集不等于没有 = = ,
/c[A-N&&Q-VY]t/   就不匹配  "ct"
3.单引号字符串与双引号字符串差别主要在控制码的转义方面,就是说单引号中的转义字符不会被转义(而"\"会占一个字符位)
  1. 'iis\now' == "iis\\now"  #=> true
复制代码
所以不会与pattern中的转义字符匹配
(可以用  p 与 print 方法分别输出 'y\nm' 与 "y\nm"  试试):
例如:
  1.     iisnow = /\n/  
  2.     haha = /s/
  3.     s1 = 'hu\ns'
  4.     s2 = "hu\ns"
  5.     p iisnow =~ s1
  6.     p iisnow =~ s2
  7.     p haha =~ s1
  8.     p haha =~ s2
  9.     exit
复制代码
(另外\t\s均无法匹配单引号字符串中的控制码,但是\s可以匹配半角空格)

4.字符簇常用小技巧:
匹配非此字符:       /[^s]/
匹配大小写的某字符: /[aA]/
……然后就不知道了 = =

本节主要内容:
1.转义字符及控制码
2.重复pattern及量词
3.MatchData类概述
4.贪婪

好吧,接着讲正文吧:
昨天说了要一下讲控制码,今天就先说了吧,顺便也把一些其他元字符说一下:
\n  匹配换行符
\t  匹配TAB
\s  空字符 相当于[ \t\r\n\f](最前面有个空格,是半角的,可以写作[[:space:]] )
(  \r  回车符、返回效果同\n差不多  
   \f  换页  均不常用 , 在处理文件的时候可能遇到)
\w  任意字母、数字外加下划线  [_0-9a-zA-Z](也可写作 [[:alnum:]] )
\d  任意数字  [0-9](也可写作  [[:digit:]] )
\W  ^\w
\D  ^\d
\S  ^\s  (可以写作  [[:graph:]] )

括号中的写法为POSIX表达式,仅供了解:(写的时候注意两个[[]],且不能往里面再加什么了)
[[:alpha:]] 等同于 [a-zA-Z]
[[:blank:]] 等同于 [ \t]
[[:cntrl:]] 匹配所以转义字符
[[:lower:]] 等同于 [a-z]
[[:upper:]] 自己配合上面那个理解
[[:print:]] 等同于 [[:graph:]] + 空格
[[:punct:]] 匹配标点符号
(上述所有的东西,都只匹配一个字符)


好了,下面开始新的东西的
讲完[] , 再说说 ?吧(那么肯定会讲 * + 咯~)
不过这之前把简单的 . 解决吧
.   等同于  ^\n  (over)

重复:
话说[]怎么匹配也只能匹配一个字符,那倘若我要匹配的字符我也不清楚有多少个怎么办呢?

下面的就 介绍几个量词,(类似于指数,用在字符之后)
?  表示0次或1次
+   表示一次或多次
*   表示0次或多次(当然1次也算)

这些符号用于单个字符的后面,即对它的紧挨着的前一个字符修饰
  1. /i+sb?[no]*/ =~ "iisnow"  #=> 0
复制代码
(注意,字符簇当做一个字符处理,我说的很多遍了)

这些字符均不能单独使用即
/*/ /?/ /+/ 都是错误的,要想匹配那个字符要转义
但是倘若用在[]里面就不需要转义,但加上"\"也行,一样的
  1. /a\*/  =~  "*aaa*" #=> 3
  2. /[a*]/ =~  "*aaa*" #=> 0
  3. /[a\*]/ =~ "\*aa*" #=> 0   
  4. # 不要误解上式,双引号字符串中 "\*" 转义为 "*" 字符,并不是匹配了"\",不信:
  5. /[a\*]/ =~ '\*aa*' #=> 1
复制代码
当然上述的多次可以更加具体一些:
需要用到"{" "}"
{a}   前一字符必须出现a次
{a,b} 前一字符出现次数 ∈ [a,b] (额,数学式子,别理解成pattern了)
  上式中a缺失 用0代替   b缺失  用+∞代替 你们懂的
于是
上述的  {a} 等同于 b = a 的情况(允许a = b但是b < a就错了)
? 等同于 {0,1}  + 等同于 {1,}  *  等同于  {0,}
若a,b同时缺失:注意{,}匹配三个字符
({}两个字符不需要转义,所以/{}/ 就是匹配 "{}" 的 写的时候别忘记的闭括号)

量词可以叠加使用(不过这样做很闲)
首先说简单的
/a+*/ 匹配效果与 /a*/ 无异
/a?+/ 匹配效果与 /a*/ 无异
可以无限的加/a?++*+++*++++/ (= =)
但是注意一点
/a+?/   /a*?/   /a??/  就不是你们想的那个意思了,下面再讲有关"贪婪"的概念

复杂一点的 {} 的叠加(这样更闲 = =)
于是我们一起探索一下:
  1. /a{1,2}{2,3}/ =~ "abaabaaabaaaabaaaaabaaaaaa" #=> 2
  2. /a{2}{2,3}/ =~ "abaabaaabaaaabaaaaabaaaaaa" #=> 9
  3. # 于是是不是说,把范围相乘就行了呢:
  4. /soi{2,3}{2}/ =~ "soiiiisoiisoii"
  5. #你们说它返回多少:0?
  6. #其实是5,就是说它匹配soiisoii
  7. /soi{2,3}{2}{2,3}/ =~ "soiiiiiiiisoiisoiisoiisoii" #=> 10
  8. /i{2,3}snow{2,3}{2}/ =~ "iisnowwiisnowwiisnowwsnoww" #=> 14
  9. /i{2,3}snow{2,3}{2}{2}/ =~ "iisnowwiisnowwiisnowwsnowwsnowwsnoww" #=> 14
  10. /i{2,3}s*now{2,3}{2}{2}/ =~ "iisnowwnowwnowwnowwnowwnowwnowwnoww" #=> 0
复制代码
规律总结一下:(话说大家实验的时候不需要想我这样写这么长……我只是自恋而已)
{}连用时,除第一个{}修饰一个字符外,后面叠加的修饰它前面的所有字符(直到遇到上一个与之没有连用的重复量词)组成的字符段

至于?、+、*与{}的混用,请大家按类似的方法探索,实际上也没什么探索的了……
但是希望大家学会这种方式

下面讲贪婪的问题之前,写要讲一下
.match方法的返回值的问题:

MatchDate类
(有于代码中的变量$~   $&   $`   $1   $2  使帖子出现问题,后面就不用代码框了,改成蓝色,鬼知道怎么回事啊)
它是一种特殊的变量类型,专为Regexp量身订做的类
用.match或.last_match得到
它包含了字符串中所有与pattern相匹配的字符段
新版本1.9.2可以通过p 方法看到字符段,但是RM不行,还是只有ID

p "snwsow".match(/s[no]w/) #=> #<MatchData: ********>  

而last_match方法引用要按照下述形式

/s[no]w/ =~ "snwsow"
p Regexp.last_match      # => #<MatchData:********>
# 而真正想要调用出它包含的匹配的字符段:
# MatchData可以像数组那样用索引调用
snow = "snwsow".match(/s[no]w/)
p snow[0] #=> "snw"

(有同学问:snow[1]怎么是nil,这以后再说,有关组群)

当然还有另一种返回结果的方式:
MatchData在返回结果时,将结果全部存放在 $~ $& $` $1 $2 ……这些奇形怪状变量之中
但是他们都不是全局变量啊,注意
其中  $~  结果为  #<MatchData:********> 等同于上述的那些做法
         $&  等同于  那个snow[0]
         $'  返回原字符串截掉被$&以及它前方的字符,即还未被匹配过的字符段

snow = "kusnwdesow".match(/s[no]w/)
p $' #=> "desow"
# 通过这个变量可以实现得到全部匹配段,用循环试一下

      $n  等同与  那个snow[n](n为正整数)
      $` 这个返回已匹配过的字符段中不匹配的部分
(右边的字符为1左边的那个键,英文输入法下面为 "`" )

snow = "kusnwdesow".match(/s[no]w/)
p $` #=> "ku"

这些变量每执行一次匹配就会改变,即反映最近一次匹配结果的值

snow = "kusnwdesow".match(/s[no]w/)
p $`   #=> "ku"
snow = "asdfsnwdesow".match(/s[no]w/)
p $`   #=> "asdf"

知道了这些,我们开始讲贪恋吧!!
我们先把匹配过程看成是pattern“吃”字符串的过程,每个pattern喜欢吃的东西都不一样,看它长的什么样就知道它喜欢吃什么
/a/ 很挑食,它只吃 "a" 这个字符
/[ab]/ 说我a,b通吃
/aaa/  说我喜欢3个a一齐吃,少一个都不行
/a+b/  
说我先找a要是发现a了就往后面找,第一个不是a的字符要是b的话,刚才的所有a和这个b我全吃了
(这就是一种意义上的“贪婪”,但不是我们要讲的)
就是说所有的a全部都被匹配

isnow = "kaaabhbi".match(/a+b/)
p $&  #=> "aaab"

倘若纯在字符簇、转义符就跟贪婪了
/.+b/
说我先把字符串分成一行行的,哪行的结尾要是b,我就把那行全吃了(这样讲好像不对……真的不对…= =)
这样说才对:我先找第一个不是换行符的字符,然后往后面找,只要不是换行符就继续找,找到了b之后,把刚才的全吃了,(嗯,是不是吃的太少了)不死心,看看后面要还不是换行符就继续找,要是惊奇的发现居然还可以吃,就继续吃
(这只是一顿哦,哦呵呵呵)

iisnow = "kacabhbi".match(/a.+b/)
p $&  #=> "acabhb"  

(在第一个b那里并不会停,会继续检阅后面,这是另一种贪婪)

当然{}也是贪婪的:

iisnow = "kaaabhbi".match(/a.{2,10}b/)
p $& #=> "aaabhb"

第一种“贪婪”并不是我们要谈论的,并且也不可避免
后一种才是我们的主题:
比如我不希望它贪婪怎么办,?帮你解决!
只要在它后面加一个?即可使它不再贪婪(现实世界这样多好啊)

iisnow = "kacabhbi".match(/a.+?b/)
p $&  #=> "acab"
iisnow = "kaaabhbi".match(/a.{2,10}?b/)
p $& #=> "aaab"

混用的时候当然大家就要注意了

OK,这一节就这么多啦
最后希望大家认真考虑  用   $`  得到所有匹配段的方法,其实挺简单的~~
(是不是要禁用编辑器代码啊……= =)


第3节
我就不另开新帖了,就这样更新吧!
我不敢连贴啊……还不是因为没有人回复,哼哼


上一次,我们匆匆讲完了贪婪的故事
(这个术语,MS在RUBY中叫贪婪,而在其他语言中就不这样叫)
这次再说一下:

1.这是一个很早就想说的问题
.match方法不一定要
string.match(pattern)的形式
pattern.match(string)是一样的

2.不贪婪即是在匹配时尽可能的少的匹配字符段,尽可能少,前提是能匹配:
  1. /i[sno]+?w/ =~ "iisnow"
复制代码
不可能因为为了匹配上最后一个字符"w"必须要把"sno"全部匹配上显得很贪婪而不进行匹配
但假如:
  1. /i[snow]+?w/ =~ "iisnowsnow"
复制代码
这时候就只会匹配到第一个"w"。

(这其实要从贪婪过程的本质——回溯说起!
  回溯过程其实还是略显复杂的,
  用我的话说就是:
  要是不贪婪最后居然导致什么都吃不到了,还不如贪婪一下呢,但是会克制一下的!)

3.上一次说到的POSIX表达式,大家要是试过就知道,要是少了一对[]会怎么样?
/[:digit:]/ =~ "iisnow3" 它会匹配什么?

4.关于.的问题
当.用于[]中时,将会只匹配"."这个字符
而多数情况下  .   是不会放入[]中的,(因为它能匹配的已经够多了)
故[.\n]并不是匹配所有字符的意思,真的要匹配所有字符
/./m
即可,m是一种修饰符,估计下一讲再讲吧~

5.上一次提出的,得到所有匹配段的问题,参考脚本如下:

  pattern = /[huaid]f/
  string = "hfujifhbdfffaf"
  string.match(pattern)
  para = []
  loop do
    para << $& if $& != nil
    break if $& == nil
    pattern =~ $'
  end
  p para

(出现了$1等等的代码段,就用蓝色代替,其他的照常)
当然啦,可以有很多种写法的,你的能否实现功能就行了啊…下面的实验就用这个表达式吧,(当然,要是嫌RM的标题画面什么的烦人的话,把代码插到Main的首行,后面再加一句:exit
就可以啦~~)

本节内容:
1.定位符
2.捕获与引用
3.选择

我们有时候会遇到这样的要求:
匹配一个以"i"开头以"w"结尾的字符串
也许你说:
/i.*w/
就可以啊……
"okaokdofkaoiisnowanosidjfoiasjdofi"
可以吗?
于是怎么去定义让i位于字符串首位,让w位于字符串末尾:
现在我们就说说几个0位匹配符(就是说不占字符位置的匹配符)
又称定位符(Anchors)

^  前面就介绍过(在[]里面,意义是取反)
而今天它的位置也是用在开头,用于匹配字符串的开头
相应的  $  就是匹配字符串的末尾了:

/^i.*w$/就可以完成上述功能了:
  1. /^i.*w$/ =~ "iisnow"
复制代码
但是大家要注意一点,我曾经说过在[]要是不用在首位,就匹配自身"^"的!
现在要是我们不把它放在pattern的首位呢?
/i^kiss^you/ =~ "i^kiss^you"
p $& #=> nil
所以说,在字符簇外面,有些特权是行不通的,转义吧!
/i\^kiss\^you/ =~ "i^kiss^you"
p $& #=> "i^kiss^you"

要是你把^不转义的放pattern的中间,是不是就不可能匹配都string呢?
  1. /.*^isnow/ =~ "isnow"
复制代码
就可以匹配,就是说^只是匹配一个位置而已,放哪里都是匹配那个位置
$和^的情况是一样的,大家请自行验证

注意
当一个字符串是多行的话,每一行的开头与结尾均是会被^与$匹配的
/^i.*w$/ =~ "iisnow\nihatesnow"
p $& #=> "iisnow"
使用那个代码(显示所有匹配段的代码)会发现后面的"ihatesnow"也可以被匹配
于是就有一个问题:结尾、开头以及换行符的顺序问题:
/w$\n^i/ =~ "iisnow\nihatesnow"
p $& #=> "w\ni"
清楚了吧~

要是说想要匹配整段,换行符的左右的头尾忽略掉,要怎么办
在pattern后面加一个"m"试试:
/^i.*w$/m =~ "iisnow\nihatesnow"
p $& #=> "iisnow\nihatesnow"
(但是实际上并不是依靠忽略换行符的左右的头尾,而是通过*的贪婪来实现的
不信:
/^i.*?w$/m =~ "iisnow\nihatesnow"
p $& #=> "iisnow"
而修饰符m的作用只不过就是将"\n"列入了被"."匹配的行列中而已


还有一种方法,便是使用\A和\z(注意是小写,大写的话后面说)
其实\A与\z,类似于^和$,匹配开头和结尾(意义很好理解哦~~)
但是区别还是有的:对比一下
/^i.*w$/ =~ "iisnow\nihatesnow"
p $& #=> "iisnow"
/\Ai.*w\z/ =~ "iisnow\nihatesnow"
p $& #=> nil
/^i.*?w$/m =~ "iisnow\nihatesnow"
p $& #=> "iisnow"
/\Ai.*?w\z/m =~ "iisnow\nihatesnow"
p $& #=> "iisnow\nihatesnow"
知道差别了吧,\A和\z才是真正的字符串的首位末尾匹配!
另外\Z就是和$一样了,遇到换行就认了…

(有人问\a是什么……是什么?我怎么知道……~~~~)

有时候我们想的并不是一段话的首位末尾的操作,我们只想看看文中处没有出现一个单词怎么办?
比如:
  1. string =<<EOF
  2. We call Ruby a transparent language. By that we mean that Ruby doesn't obscure
  3. the solutions you write behind lots of syntax and the need to churn out reams
  4. of support code just to get simple things done. With Ruby you write programs
  5. close to the problem domain. Rather than constantly mapping your ideas and
  6. designs down to the pedestrian level of most languages, with Ruby you'll find
  7. you can express them directly and express them elegantly. This means you code
  8. faster. It also means your programs stay readable and maintainable.
  9. EOF
复制代码
这样的一个字符串中
(不知道多行字符串输入的同志,看看帮助吧,真不懂的话,
p string
看string是什么,有个大致的概念,以后我们再讲多行字符串输入吧)

匹配所有的 at 单词
事实上,里面没有at这个单词= =,那么为什么还是告诉我们,匹配了3个呢?
因为像that这种杂货混在里面了……怎么办

\b帮你解决,类似于$与^的零字符匹配,\b匹配的是单词的边界
即一个字符与空格或换行符的交界处,理解了$、^了之后,\b应该不难理解
\B就是\b的反面,即非单词边界

/\biisnow/就是指以iisnow开头的单词
/iisnow\b/就是指以iisnow结尾的单词
/\biisnow\b/就是专指iisnow这个单词了(实际上iisnow不是个单词= =)

下面转一下折(其实这句话用来承上启下很简洁,不是吗?)
到今天为止讲完了"[]"的字符簇,"{}"在重复时的应用,现在讲讲
"()"的用法:
他们的功能很多,一个个的说吧:
事先来个大致的了解吧:
/i(is)(no)w/ =~ "iisnow"
p $& #=> "iisnow"
看起来,貌似没有什么区别啊!
恩~那么
p $1  #=> "is"
p $2  #=> "no"

试试:
怎么样昨天说的改一下:

snow = "snwsow".match(/s([no])w/)
  p snow[0] #=> "snw"
  p snow[1] #=> "n"
  p snow[2] #=> nil

就是说()对应的段所匹配的字符段会赋值给$1,$2,$3……
(其实这很方便我们研究的)
第一个()对应$1,依次类推,没有就nil

()的这种功能叫“捕获”(Capturing)
捕获的字符段有什么用呢?用于确定字符块、还有用于引用!

确定字符块,
重复量词的作用域只有一个字符,但是()也可以跟上
代表()内所有表达式的重复修饰:

/(is)+(sb)*(now)?(hah){1,4}/ =~ "isisnowhahhah"
  p $& #=> "isisnowhahhah"
  p $1 #=> "is"
  p $2 #=> nil
  p $3 #=> "now"
  p $4 #=> "hah"

再说说引用吧
有时候我们需要这样很强限制的匹配
/s..ws..w/
我们想要它匹配类似于
snowsnow
snuwsnuw
snmwsnmw
的字符串
而不要匹配
snowsnuw
snowsnmw
的字符串怎么办?
直接这样肯定不行:
  1. /s(..)ws(..)w/ =~ "snowsnowsnowsnuwsnuwsnuw"
复制代码
得到的para(前面的代码啊!)
["snowsnow","snowsnuw","snuwsnuw"]
似乎ms有点难度
而引用就可以解决这一点:使用\配上数字进行引用
  1. /s(..)ws\1w/ =~ "snowsnowsnowsnuwsnuwsnuw"
复制代码
para 就是 ["snowsnow","snuwsnuw"]了
这就是引用的作用
即\1会引用第一个()所匹配的字符段放在\1所在的位置!
同理\2,\3就都懂了吧!
引用同样可以用重复量词修饰:
/i(.)\1*w/ =~ "innnw"
p $& #=> "innnw"
p $1 #=> "n"
当引用遇上贪婪:
  1. /s(.+)(.*)ws\1w/ =~ "snowsnowsnowsnuwsnuwsnuwsnowsnuwsnow"
  2. /s(.+?)(.*?)ws\1w/ =~ "snowsnowsnowsnuwsnuwsnuwsnowsnuwsnow"
复制代码
其实原则就是尽量贪婪(或不贪婪)前提是要匹配得上,结合引用一起考虑即可

引用不止可以用1、2、3、4这几个俗套的名字,在很长的pattern里面,要引用前面所捕获的内容,没有标示名,是会很蛋疼的,于是乎,()是可以命名的!
命名方法在括号最前面加上  ?<>  <>里面写上标示名称即可
后面引用的话使用\k<>   <>里面写上同样的标示名称即可
  1. /(?<name>\w+):(?<age>\d+)\n\k<age>\k<name>/ =~ "iisnow:99\n99iisnow"
复制代码
(我才不会告诉你们我多少岁了呢~)

(下面讲讲一下RM用不上的功能!)
而后面要调用匹配捕获的结果:
  1. iisnow = "iisnow:99\n99iisnow".match(/(?<name>\w+):(?<age>\d+)\n\k<age>\k<name>/)
  2.   p iisnow[0]
  3.   p iisnow[1]
  4.   p iisnow[2]
复制代码
都是可以显示的,但是那要标示名称干什么?
p iisnow[:name]
p iisnow[:age]
(MS RM是不支持的,会报错)

下面回归正题:()的嵌套以及子表达式的引用
当()嵌套使用时,引用的次序不管是不是子(),从左往右按"("的次序排列
/i(..(..))w/ =~ "iisnow"
  p $&  #=> "iisnow"
  p $1  #=> "isno"
  p $2  #=> "no"

(下面又是RM不支持的功能)
如果在括号中引用该括号的捕获内容会怎么样?
如:
/i(.\1)w/ 这似乎就成了无限的循环了……是吧,有什么办法实现这种引用呢?
\g就可以
/i(.\g)w/ 就可以,既然不支持,就不多说了

再说说()的另一个功能吧:
选择(Alternation),需要用到 | 操作符
  1. /i(is|are)now/ =~ "iisnowiarenow"
复制代码
使用最开始的那个代码:para就为:
["iisnow"、"iarenow"]
其实就是说 | 操作符对字符段进行了OR的操作,类似于[]字符簇的功能,只是它是按字符段来进行OR的,当然 | 可以连用: /i(is|are|was|were|will)now/ 均可

但是注意,这样的选择,不同于捕获,是不会给$1等等赋值的!
/i(is|are)now/ =~ "iisnowiarenow"
p $1 #=> nil

今天就这么多吧,好了,下次再讲吧,


评分

参与人数 1星屑 +24 收起 理由
纳尔德 + 24 我很赞同

查看全部评分

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-15 20:42

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表