V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
icerunz
V2EX  ›  问与答

请教一个正则表达式,从vcf文件中匹配信息

  •  
  •   icerunz · 2011-09-08 21:19:15 +08:00 · 4947 次点击
    这是一个创建于 4825 天前的主题,其中的信息可能已经有所发展或是发生改变。
    在两个手机之间倒腾通讯录,结果WM这边出来的是一个csv文件,经过处理之后形成一个集合了1000多个联系人的vcf文件,目标手机只认单个vcf文件(每个联系人一个单独的vcf)。于是就想用正则从多个联系人集合的那个vcf中匹配出来之后另存为单个vcf。

    上面啰嗦了半天也不直观,也就是说从类似下面的这个文件中匹配出单个的联系人信息:

    BEGIN:VCARD
    VERSION:2.1
    N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E9=98=BF=E6=97=BA=E5=93=E9=99=A2
    TEL;CELL:13500000000
    END:VCARD

    BEGIN:VCARD
    VERSION:2.1
    N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E9=B4=E5=93=A5
    TEL;CELL:13700000000
    END:VCARD


    也就是以BEGIN:VCARD开头,END:VCARD结尾的信息。
    我用PHP写,现在用的表达式是:

    $ereg = "|BEGIN:VCARD([\s\S]+?)END:VCARD|";

    这样的话结果是两个数组,每个数组里面包含了400多个联系人信息。。。也就是说跨越了很多的END:VCARD才匹配出了一个,查了半天不知道什么原因,特请教


    不胜感激。
    23 条回复    1970-01-01 08:00:00 +08:00
    icerunz
        1
    icerunz  
    OP
       2011-09-08 21:19:47 +08:00
    是不是换行符一类的匹配出错的原因?
    Livid
        2
    Livid  
    MOD
       2011-09-08 21:21:54 +08:00
    用一个循环逐行处理吧。
    manhere
        3
    manhere  
       2011-09-08 21:31:26 +08:00
    gawk for windows
    bhuztez
        4
    bhuztez  
       2011-09-08 21:36:15 +08:00
    regex是可以的吧,

    /(BEGIN:VCARD\n((?!END:VCARD)[^\n]*\n)*END:VCARD)/

    临时用 Python 写了下

    #!/usr/bin/env python2

    import sys, re

    data = sys.stdin.read()

    for m in re.findall(r'(BEGIN:VCARD\n((?!END:VCARD)[^\n]*\n)*END:VCARD)', data, re.M):
    print '**********'
    print m[0]
    bhuztez
        5
    bhuztez  
       2011-09-08 21:37:42 +08:00
    print 前面的缩进咋没了,囧
    icerunz
        6
    icerunz  
    OP
       2011-09-08 21:40:44 +08:00
    @Livid 求教,逐行处理的大概思路是?
    icerunz
        7
    icerunz  
    OP
       2011-09-08 21:42:34 +08:00
    @bhuztez
    这个/(BEGIN:VCARD\n((?!END:VCARD)[^\n]*\n)*END:VCARD)/
    用preg_match_all();
    出来的结果是:
    array(3) { [0]=> array(0) { } [1]=> array(0) { } [2]=> array(0) { } }
    bhuztez
        8
    bhuztez  
       2011-09-08 21:44:09 +08:00
    @icerunz 我是用 Python 的那个 re 的,PHP里有多个regex库,不是每个用法和 Python 那个一样的,注意,最前最后的 // 表示的是这里面是一个正则表达式
    icerunz
        9
    icerunz  
    OP
       2011-09-08 21:47:20 +08:00
    不好意思,我看错一个地方,用
    $ereg = "|BEGIN:VCARD([\s\S]+?)END:VCARD|";
    就解决了。

    原来写的中间部分是([\s\S]+),后来查了书试着写了个?在里面,成功匹配了。但是还是不大清楚其中的原理……求教……
    icerunz
        10
    icerunz  
    OP
       2011-09-08 21:49:44 +08:00
    @bhuztez 恩PCRE版本的都有个起始符和结束符。
    我现在有点搞不清[\s\S]+?和[\s\S]??的区别
    Hyperion
        11
    Hyperion  
       2011-09-08 22:13:46 +08:00
    @icerunz +一次以上, ?是一次或者零次, 那么就推出...

    [a]+? 尽可能少的重复, 至少0次(空位算进去了).
    "aabaaa" => array("a", "a", "a", "a", "a");

    [a]?? 尽可能少的重复, 至少0次(空位算进去了).
    "aaabaa" => array("", "a", "", "a", "", "a", "", "", "a", "", "a", "");

    ??匹配起来太奇怪, 我从来没有用过...
    Hyperion
        12
    Hyperion  
       2011-09-08 22:17:24 +08:00
    @Hyperion

    [a]+? 至少1次, 尽可能少的重复 = [a]{1}
    "aabaaa" => array("a", "a", "a", "a", "a");

    = =+ 修正一下
    noahasm
        13
    noahasm  
       2011-09-08 22:21:38 +08:00
    楼主建个临时目录 tmpvcf, 假设大 vcf 文件在其中,名为 all.vcf, 如下命令:


    perl -ne 'BEGIN{$/="END:VCARD"} s/^\s+[\r\n]+//; /\w/ && qx/echo "$_" > ${.}.vcf/' all.vcf


    应该就 ok 了
    icerunz
        14
    icerunz  
    OP
       2011-09-09 00:02:34 +08:00
    @noahasm 传说中处理文本牛逼的Perl……
    icerunz
        15
    icerunz  
    OP
       2011-09-09 00:03:19 +08:00
    @noahasm 能分段讲解一下不
    icerunz
        16
    icerunz  
    OP
       2011-09-09 00:04:15 +08:00
    @Hyperion

    表5.懒惰限定符
    *? 重复任意次,但尽可能少重复
    +? 重复1次或更多次,但尽可能少重复
    ?? 重复0次或1次,但尽可能少重复
    {n,m}? 重复n到m次,但尽可能少重复
    {n,}? 重复n次以上,但尽可能少重复

    这个资料还是不错的: http://www.cnblogs.com/deerchao/archive/2006/08/24/zhengzhe30fengzhongjiaocheng.html
    noahasm
        17
    noahasm  
       2011-09-09 12:25:45 +08:00
    @icerunz 如下:


    perl -ne 'BEGIN{$/="END:VCARD"} s/^\s*[\r\n]+//; /\w/ && qx/echo "$_" > ${.}.vcf/' all.vcf

    && 就是 and, 左边成立右边才求值(运行)
    qx// 进行系统调用,和 system 或反引号功能类似,
    ${.} 其实就是 $., 花括号只是为了和后面 .vcf 的点区分开来

    上面的单行命令等效于 perl 程序:

    #!/usr/bin/env perl
    my $file = shift; # shift 会取得命令行的第一个参数
    $/ = "END:VCARD"; # $/ 指换行符,设为END:VCARD后,perl会以它为每行的结束标记,而不是默认的"\n"
    open(F, $file) or die $!;
    while (<F>) {
    next unless /\w/; # 如果当前行不含a-zA-Z0-9_这些字符,直接跳过
    s/^\s*[\r\n]+//; # 删除空白内容
    my $line_num = $.; # $. 指当前行号
    system "echo \"$_\" > $line_num.vcf"; # system 调用,通过 echo 写入文件
    }
    close F;

    把这段 perl 程序寸成 t.pl, 然后运行:

    perl t.pl all.vcf

    效果是一样的.
    icerunz
        18
    icerunz  
    OP
       2011-09-09 21:26:28 +08:00
    @noahasm 非常感谢,Perl处理文本真的不是一般NB,再仔细看看。
    然后有个疑问,这一句:

    $/ = "END:VCARD"; # $/ 指换行符,设为END:VCARD后,perl会以它为每行的结束标记,而不是默认的"\n"

    中提到的制定END:VCARD为换行符,那是否就表示每一个区块当中在END:VCARD之前的/n都会被忽略掉,而以END:VCARD作为行结尾,整个信息块就被作为一行来处理?

    .号在Perl里面有特定的含义么?
    noahasm
        19
    noahasm  
       2011-09-09 22:13:17 +08:00
    @icerunz 换行符代表一行结束,下起新行,也就是说程序是根据换行符来断行的,在碰到换行符前的所有内容,包括换行符本身算作“一行”内容,和你理解的一样。

    . 号在 Perl 里如果不加引号什么的,被称为句点操作符,一般用来连接字符串。
    在正则里它是一个元字符,具体可查 http://perldoc.perl.org/perlretut.html

    $. 是 Perl 的特殊变量,代表当前读入行的行号。

    其实这些功能,其他语言也都能做到,比如 python, ruby 等,只不过我用 Perl 最顺手,最熟悉
    icerunz
        20
    icerunz  
    OP
       2011-09-09 22:56:09 +08:00
    @noahasm 谢谢!了解了。开始学习文本处理方面的内容,还有很大差距啊。
    你用几种语言?Perl只用作文本方面的处理吧?
    args
        21
    args  
       2011-09-09 23:49:20 +08:00
    正则表达是默认是贪婪模式。
    noahasm
        22
    noahasm  
       2011-09-10 02:36:23 +08:00
    @icerunz 我最熟的就是 perl,在学 python/objc, 我用 perl 做各种事情,web应用,图表,爬虫,数据库,文本处理,网络通信等
    icerunz
        23
    icerunz  
    OP
       2011-09-10 12:52:38 +08:00
    @noahasm 恩,领教了。看来语言并不是阻碍应用的直接原因。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2758 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 08:24 · PVG 16:24 · LAX 00:24 · JFK 03:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.