lua 正则匹配

在OpenResty中,同时存在两套正则表达式规范: Lua 语言的规范和Nginx的规范,即使您对 Lua 语言中的规范非常熟悉,我们仍不建议使用 Lua 中的正则表达式。一是因为 Lua 中正则表达式的性能并不如 Nginx 中的正则表达式优秀;二是 Lua 中的正则表达式并不符合 POSIX 规范,而 Nginx 中实现的是标准的 POSIX 规范,后者明显更具备通用性。

Lua 中的正则表达式与Nginx中的正则表达式相比,有5%-15%的性能损失,而且Lua将表达式编译成Pattern之后,并不会将Pattern缓存,而是每此使用都重新编译一遍,潜在地降低了性能。 Nginx 中的正则表达式可以通过参数缓存编译过后的Pattern,不会有类似的性能损失。

o选项参数用于提高性能,指明该参数之后,被编译的Pattern将会在worker进程中缓存,并且被当前worker进程的每次请求所共享。Pattern缓存的上限值通过lua_regex_cache_max_entries来修改。

--nginx.conf
location /test {
    content_by_lua '
        local regex = [[\\d+]]

        -- 参数"o"是开启缓存必须的
        local m = ngx.re.match("hello, 1234", regex, "o")  
        if m then
            ngx.say(m[0])
        else
            ngx.say("not matched!")
        end
    ';
}
--在网址中输入"yourURL/test",即会在网页中显示1234。

ngx.re.match

语法:captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)

只有第一次匹配的结果被返回,如果没有匹配,则返回nil;或者匹配过程中出现错误时,也会返回nil,此时错误信息会被保存在err中。

当匹配的字符串找到时,一个Lua table captures会被返回,captures[0]中保存的就是匹配到的字串,captures[1]保存的是用括号括起来的第一个子模式的结果,captures[2]保存的是第二个子模式的结果,依次类似。

    local m, err = ngx.re.match("hello, 1234", "[0-9]+")
    if m then
        -- m[0] == "1234"
    else
        if err then
            ngx.log(ngx.ERR, "error: ", err)
            return
        end

        ngx.say("match not found")
    end



上面例子中,匹配的字符串是1234,因此m[0] == "1234",但是没有用括号括起来的子模式,因此,m[1],m[2]等均为nil。



    local m, err = ngx.re.match("hello, 1234", "([0-9])[0-9]+")
    -- m[0] == "1234"
    -- m[1] == "1"

命名方式的捕获,从v0.7.14版本后开始支持,如下所示:

例1:

    local m, err = ngx.re.match("hello, 1234", "([0-9])(?<remaining>[0-9]+)")
    -- m[0] == "1234"
    -- m[1] == "1"
    -- m[2] == "234"
    -- m["remaining"] == "234"

例2:

    local m, err = ngx.re.match("hello, world", "(world)|(hello)|(?<named>howdy)")
    -- m[0] == "hello"
    -- m[1] == nil
    -- m[2] == "hello"
    -- m[3] == nil
    -- m["named"] == nil

例2中,为什么m[1]等于nil?因为在给定的模式串中,最先匹配的是hello,因此,其他子模式在找到的匹配串中查找不到对应的匹配串,因此,除了hello子模式外,其他的子模式的匹配结果都是nil。一定要记住,是谁最先被匹配的。

options选项可以是下面的取值的组合:

a             锚定模式,只从头开始匹配. 

d             DFA模式,或者称最长字符串匹配语义,需要PCRE 6.0+支持.

D             允许重复的命名的子模式,该选项需要PCRE 8.12+支持,例如

                local m = ngx.re.match("hello, world",
                                       "(?<named>\w+), (?<named>\w+)",
                                       "D")
                -- m["named"] == {"hello", "world"}

i             大小写不敏感模式.


j             启用PCRE JIT编译, 需要PCRE 8.21+ 支持,并且必须在编译时加上选项--enable-jit,

                为了达到最佳性能,该选项总是应该和'o'选项搭配使用.         


J             启用PCRE Javascript的兼容模式,需要PCRE 8.12+ 支持. 


m             多行模式.


o             一次编译模式,启用worker-process级别的编译正则表达式的缓存.


s             单行模式.


u             UTF-8模式. 该选项需要在编译PCRE库时加上--enable-utf8 选项.

U             与"u" 选项类似,但是该项选禁止PCRE对subject字符串UTF-8有效性的检查.

x             扩展模式

两个例子:

    local m, err = ngx.re.match("hello, world", "HEL LO", "ix")
    -- m[0] == "hello"



    local m, err = ngx.re.match("hello, 美好生活", "HELLO, (.{2})", "iu")
    -- m[0] == "hello, 美好"
    -- m[1] == "美好"

第四个可选参数ctx可以传入一个Lua Table,传入的Lua Table可以是一个空表,也可以是包含pos字段的Lua Table。如果传入的是一个空的Lua Table,那么,ngx.re.match将会从subject字符串的起始位置开始匹配查找,查找到匹配串后,修改pos的值为匹配字符串的下一个位置的值,并将pos的值保存到ctx中,如果匹配失败,那么pos的值保持不变;如果传入的是一个非空的Lua Table,即指定了pos的初值,那么ngx.re.match将会从指定的pos的位置开始进行匹配,如果匹配成功了,修改pos的值为匹配字符串的下一个位置的值,并将pos的值保存到ctx中,如果匹配失败,那么pos的值保持不变。

    local ctx = {}
    local m, err = ngx.re.match("1234, hello", "[0-9]+", "", ctx)
         -- m[0] = "1234"
         -- ctx.pos == 5



    local ctx = { pos = 2 }
    local m, err = ngx.re.match("1234, hello", "[0-9]+", "", ctx)
         -- m[0] = "34"
         -- ctx.pos == 5

注意:

如果需要传入ctx参数,但并不需要第三个可选参数options时,第三个参数也不能简单去掉,这时需要传入一个空的字符串作为第三个参数的值。

第四个可选参数还不是很熟悉,暂且留空。

ngx.re.find

语法:from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)

该方法与ngx.re.match方法基本类似,不同的地方在于ngx.re.find返回的是匹配的字串的起始位置索引和结束位置索引,如果没有匹配成功,那么将会返回两个nil,如果匹配出错,还会返回错误信息到err中。

例子:

    local s = "hello, 1234"
    local from, to, err = ngx.re.find(s, "([0-9]+)", "jo")
    if from then
        ngx.say("from: ", from)
        ngx.say("to: ", to)
        ngx.say("matched: ", string.sub(s, from, to))
    else
        if err then
            ngx.say("error: ", err)
            return
        end
        ngx.say("not matched!")
    end



输出结果:

from: 8

to: 11

matched: 1234

该方法相比ngx.re.match,不会创建新的Lua字符串,也不会创建新的Lua Table,因此,该方法比ngx.re.match更加高效,因此,在可以使用ngx.re.find的地方应该尽量使用。

第五个参数可以指定返回第几个子模式串的起始位置和结束位置的索引值,默认值是0,此时将会返回匹配的整个字串;如果nth等于1,那么将返回第一个子模式串的始末位置的索引值;如果nth等于2,那么将返回第二个子模式串的始末位置的索引值,依次类推。如果nth指定的子模式没有匹配成功,那么将会返回两个nil。

    local str = "hello, 1234"
    local from, to = ngx.re.find(str, "([0-9])([0-9]+)", "jo", nil, 2)
    if from then
        ngx.say("matched 2nd submatch: ", string.sub(str, from, to))  -- yields "234"
    end

ngx.re.gmatch

语法:iterator, err = ngx.re.gmatch(subject, regex, options?)

与ngx.re.match相似,区别在于该方法返回的是一个Lua的迭代器,这样就可以通过迭代器遍历所有匹配的结果。

如果匹配失败,将会返回nil,如果匹配出现错误,那么还会返回错误信息到err中。



    local iterator, err = ngx.re.gmatch("hello, world!", "([a-z]+)", "i")
    if not iterator then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

    local m
    m, err = iterator()    -- m[0] == m[1] == "hello"
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

    m, err = iterator()    -- m[0] == m[1] == "world"
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

    m, err = iterator()    -- m == nil
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end



更多情时候,只需要把迭代过程放入一个while循环中即可:

    local it, err = ngx.re.gmatch("hello, world!", "([a-z]+)", "i")
    if not it then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

    while true do
        local m, err = it()
        if err then
            ngx.log(ngx.ERR, "error: ", err)
            return
        end

        if not m then
            -- no match found (any more)
            break
        end

        -- found a match
        ngx.say(m[0])
        ngx.say(m[1])
    end

options选项的使用与ngx.re.match中的options选项的用法是一样的。

注意点:

ngx.re.gmatch返回的迭代器只能在一个请求所在的环境中使用,就是说,我们不能把返回的迭代器赋值给持久存在的命名空间(比如一个Lua Packet)中的某一个变量。

ngx.re.sub

语法:newstr, n, err = ngx.re.sub(subject, regex, replace, options?)

该方法主要实现匹配字符串的替换,会用replace替换匹配的字串,replace可以是纯字符串,也可以是使用 0, 1等子模式串的形式,ngx.re.sub返回进行替换后的完整的字符串,同时返回替换的总个数;options选项,与ngx.re.match中的options选项是一样的。

    local newstr, n, err = ngx.re.sub("hello, 1234", "([0-9])[0-9]", "[$0][$1]")
    if newstr then
        -- newstr == "hello, [12][1]34"
        -- n == 1
    else
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

在上面例子中, 0 1表示第一个子模式匹配的字串,以此类推。

可以用大括号{}将相应的0,1,2…括起来,以区分一般的数字:

    local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "${0}00")
        -- newstr == "hello, 100234"
        -- n == 1

如果想在replace字符串中显示 进行转义(不要用反斜杠$对美元符号进行转义,这种方法不会得到期望的结果):

    local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "$$")
        -- newstr == "hello, $234"
        -- n == 1

如果replace是一个函数,那么函数的参数是一个”match table”, 而这个”match table”与ngx.re.match中的返回值captures是一样的,replace这个函数根据”match table”产生用于替换的字符串。

    local func = function (m)
        return "[" .. m[0] .. "][" .. m[1] .. "]"
    end
    local newstr, n, err = ngx.re.sub("hello, 1234", "( [0-9] ) [0-9]", func, "x")
        -- newstr == "hello, [12][1]34"
        -- n == 1

注意:

通过函数形式返回的替换字符串中的美元符号$不再是特殊字符,而只是被看作一个普通字符。

ngx.re.gsub

语法:newstr, n, err = ngx.re.gsub(subject, regex, replace, options?)

该方法与ngx.re.sub是类似的,但是该方法进行的是全局替换。

看两个例子:

    local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", "[$0,$1]", "i")
    if newstr then
        -- newstr == "[hello,h], [world,w]"
        -- n == 2
    else
        ngx.log(ngx.ERR, "error: ", err)
        return
    end



    local func = function (m)
        return "[" .. m[0] .. "," .. m[1] .. "]"
    end
    local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", func, "i")
        -- newstr == "[hello,h], [world,w]"
        -- n == 2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值