精通正则表达式之重复单词程序详解

March 20th, 2010 Categories: 问答

chinaunix论坛上有这样一问:
搜索重复单词(“this this”)问题,问为什么第4行要加^:

1
2
3
4
5
6
7
    $/ = ".\n";
    while (<>) {
      next if !s/\b([a-z]+)((?:\s<<[^>]+>)+)(\1\b)/\e[7m$1\e[m$2\e[7m$3\e[m/ig;
      s/^(?:[^\e]*\n)+//mg;   # Remove any unmarked lines.   # 为何需要加^
      s/^/$ARGV: /mg;         # Ensure lines begin with filename.
      print;
    }

要解答这个问题,不能只print "hello world";着眼于这一行。程序不长,从头到尾详细读一下,这个问题就兵不血刃地解决了。

  • $/ = ".\n";这一行的作用是设置分行符. 如果在程序中使用了while(<>)这样的东东, 就可以使用perl script.pl file.txt这样调用该perl脚本了, 它源源不断地读入file.txt中的内容. $/是分行符, 其默认值是newline. 默认情况下, 每个while的循环处理file.txt中的一行. 如果将 $/ 定义为undef, 即在程序开头 undef $/ 一下, file.txt中的全部内容就都读入到一个变量$_中去了.
    本程序中, 换行符定义为.\n, 其含义就是, 将以点号后跟一个\n视为换行符. 这样就忽略了普通的折行的情况, 使程序更加智能, 可以处理一行行尾是this, 下行行首还是this的重复词的情况. 关于$/的详细资料, 请参考perl 文档.
  • while (<>) {这一行无须细说.
  • next if !s/\b([a-z]+)((?:\s|<[^>]+>)+)(\1\b)/\e[7m$1\e[m$2\e[7m$3\e[m/ig;这一行需要说一下. 其框架虽然是next if !s///, 看起来什么也没做似的, 但是一点儿都没偷懒. 它先判断该行是否匹配某模式, 如果匹配则进行替换(至于如何替换, 下文交待); 如果不匹配, 才提前结束本次循环, 进入下一次循环.
    现在再来看一下它是如何进行替换的. 看上去很复杂, 但是并不是不可理解. 我们先去掉\e[m之类的东东, 这是在bash下对字符进行高亮控制的.
    s/\b([a-z]+)((?:\s|<[^>]+>)+)(\1\b)/$1 $2 $3/ig;
    这样是不是清晰多了? 后边的$1, $2, $3不去管它, 我们只看一下匹配部分的正则式:
    \b([a-z]+)((?:\s|<[^>]+>)+)(\1\b)
    左边界符; 一个英文单词; 一个或多个空白字符或<...>; 前边出现过的那个英文单词再次出现; 右边界符.
    很清晰嘛!
  • s/^(?:[^\e]*\n)+//mg;   # Remove any unmarked lines.   # 为何需要加^
    这一句很简练. 它的作用是, 将没有出现重复词的行去掉. 请注意, 这里的行, 是指逻辑行, 而非物理行. 程序一开始就说了, 以.\n结尾的才算一行(物理行).
    ^(?:[^\e]*\n) 这是指整个逻辑行中, 从行首到\n, 全是由0个或多个非\e组成, 即该行未被插入高亮控制字符, 亦即该行原来不存在重复词.
    本正则替换语句使用的模式是mg, 即^匹配中间的逻辑行的行首; 且整个字串或许包含多个逻辑行, 因此使用g来进行全局替换.
  • s/^/$ARGV: /mg;这是让输出结果的每一行的行首(同样是逻辑行), 插入文件名和冒号. 匹配模式mg的含义同上.
  • print;这是打印输出默认变量$_的值. 上面所有没有出现”主语”的, 都默认是对$_进行操作. 地球人都知道.

现在再来看一个例子. 对于文本文件a.txt:

1
2
3
4
5
6
this is a apple.
this is a red red apple.
i love regex
i love regular expressions
this is a fat
<very>fat</very> pig.

它是怎么被程序处理的呢?

首先跟据$/的设定, 文本文件被分为这样几行:

  1. 1
    this is a apple.
  2. 1
    this is a red red apple.
  3. 1
    2
    3
    4
    i love regex
    i love regular expressions
    this is a fat
    <very>fat</very> pig.

为什么后三行被划到一起呢? 因为分行符是.\n.

处理步骤:

  • 程序在处理第一组
    1
    this is a apple.

    时, 直接next过, 不予操作;

  • 1
    this is a red red apple.

    red red被高亮, 打印输出.

  • 1
    2
    3
    4
    i love regex
    i love regular expressions
    this is a fat
    <very>fat</very> pig.

    fat fat被高亮; i love regex以及i love regular expressions被s/^(?:[^\e]*\n)+//mg击中, 分别删除(替换为空).

解释完毕.

Tags:
No comments yet.

Leave a Comment