如何匹配正则表达式中的数值?

时间:2010-08-05 02:05:05

标签: regex range numbers match

好的,这是我自己进行的一项非常有趣的挑战。

我的RegEx作为输入行,如下所示:

147.63.23.156/159
94.182.23.55/56
134.56.33.11/12

我需要输出一个与表示范围匹配的正则表达式让我解释一下。

例如,如果RegEx收到147.63.23.156/159,则需要输出与以下内容匹配的RegEx:

147.63.23.156
147.63.23.157
147.63.23.158
147.63.23.159

我该怎么做?

目前我有:

(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})/(\d{1,3})
  • $ 1包含第一个xxx.xxx.xxx.部分
  • $ 2包含数字的较低范围
  • $ 3包含数字的上限范围

5 个答案:

答案 0 :(得分:1)

如果你只是需要在一次构建它们,this website就可以了。

如果您需要代码,并且不介意python,this code does it可用于任意数值范围。

答案 1 :(得分:1)

正则表达式确实不是验证IP地址的好方法,我希望事先清楚说明。解析地址并进行一些简单的算术来比较它们要容易得多。一些不到和超过的,你就在那里。

那就是说,编写这个正则表达式生成器似乎是一个非常有趣的练习。你知道吗?它是!我想出了一大堆Python代码来生成这些正则表达式。在我展示代码之前,这里是它为几个IP范围生成的正则表达式样本:

1.2.3.4 to 1.2.3.4              1\.2\.3\.4

147.63.23.156 to 147.63.23.159  147\.63\.23\.15[6-9]

10.7.7.10 to 10.7.7.88          10\.7\.7\.([1-7]\d|8[0-8])

127.0.0.0 to 127.0.1.255        127\.0\.[0-1]\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))

我将分两部分展示代码。首先,为简单整数范围生成正则表达式的部分。第二,处理完整IP地址的部分。

匹配的数字范围

第一步是弄清楚如何生成一个匹配任意整数范围的正则表达式,比如12-28或0-255。

@Robert Harvey已经发布了一些链接到一些代码来做到这一点,但是很好!在看到他的链接之前,我在编写代码的过程中做得很好。此外,它是关于旅程而不是目的地,对吗?

所以我编写了自己的数字范围正则表达式生成器的实现。以下是它出现的正则表达式的一个例子:

156 to 159   15[6-9]

1 to 100     [1-9]|[1-9]\d|100

0 to 255     \d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5])

这是代码。有许多评论内联解释其背后的逻辑。总的来说,它依赖于大量的递归和特殊的套管来试图保持正则表达式的精益和平均值。

import sys, re

def range_regex(lower, upper):
    lower, upper = str(lower), str(upper)

    # Different lengths, for instance 1-100. Combine regex(1-9) and
    # regex(10-100).
    if len(lower) != len(upper):
        return '%s|%s' % (
            range_regex(lower, '9' * len(lower)),
            range_regex(10 ** (len(lower)), upper)
        )

    ll, lr = lower[0], lower[1:]
    ul, ur = upper[0], upper[1:]

    # One digit numbers.
    if lr == '':
        if ll == '0' and ul == '9':
            return '\\d'
        else:
            return '[%s-%s]' % (ll, ul)

    # Same first digit, for instance 12-14. Concatenate "1" and regex(2-4).
    elif ll == ul:
        return ll + sub_range_regex(lr, ur)

    # All zeros to all nines, for instance 100-399. Concatenate regex(1-3)
    # and the appropriate number of \d's.
    elif lr == '0' * len(lr) and ur == '9' * len(ur):
        return range_regex(ll, ul) + '\\d' * len(lr)

    # All zeros on left, for instance 200-649. Combine regex(200-599) and
    # regex(600-649).
    elif lr == '0' * len(lr):
        return '%s|%s' % (
            range_regex(lower, str(int(ul[0]) - 1) + '9' * len(ur)),
            range_regex(ul + '0' * len(ur), upper)
        )

    # All nines on right, for instance 167-499. Combine regex(167-199) and
    # regex(200-499).
    elif ur == '9' * len(ur):
        return '%s|%s' % (
            range_regex(lower, ll + '9' * len(lr)),
            range_regex(str(int(ll[0]) + 1) + '0' * len(lr), upper)
        )

    # First digits are one apart, for instance 12-24. Combine regex(12-19)
    # and regex(20-24).
    elif ord(ul[0]) - ord(ll[0]) == 1:
        return '%s%s|%s%s' % (
            ll, sub_range_regex(lr, '9' * len(lr)),
            ul, sub_range_regex('0' * len(ur), ur)
        )

    # Far apart, uneven numbers, for instance 15-73. Combine regex(15-19),
    # regex(20-69), and regex(70-73).
    else:
        return '%s|%s|%s' % (
            range_regex(lower, ll + '9' * len(lr)),
            range_regex(str(int(ll[0]) + 1) + '0' * len(lr),
                        str(int(ul[0]) - 1) + '9' * len(ur)),
            range_regex(ul + '0' * len(ur), upper)
        )

# Helper function which adds parentheses when needed to sub-regexes.
# Sub-regexes need parentheses if they have pipes that aren't already
# contained within parentheses. For example, "6|8" needs parentheses
# but "1(6|8)" doesn't.
def sub_range_regex(lower, upper):
    orig_regex = range_regex(lower, upper)
    old_regex  = orig_regex

    while True:
        new_regex = re.sub(r'\([^()]*\)', '', old_regex)

        if new_regex == old_regex:
            break
        else:
            old_regex = new_regex
            continue

    if '|' in new_regex:
        return '(' + orig_regex + ')'
    else:
        return orig_regex

匹配的IP地址范围

有了这个功能,我就可以使用非常相似的IP范围功能来处理完整的IP地址。代码与上面的代码非常相似,只是我们在base 256而不是base 10中工作,代码抛出了列表而不是字符串。

import sys, re, socket

def ip_range_regex(lower, upper):
    lower = [ord(c) for c in socket.inet_aton(lower)]
    upper = [ord(c) for c in socket.inet_aton(upper)]

    return ip_array_regex(lower, upper)

def ip_array_regex(lower, upper):
    # One octet left.
    if len(lower) == 1:
        return range_regex(lower[0], upper[0])

    # Same first octet.
    if lower[0] == upper[0]:
        return '%s\.%s' % (lower[0], sub_regex(ip_array_regex(lower[1:], upper[1:])))

    # Full subnet.
    elif lower[1:] == [0] * len(lower[1:]) and upper[1:] == [255] * len(upper[1:]):
        return '%s\.%s' % (
            range_regex(lower[0], upper[0]),
            sub_regex(ip_array_regex(lower[1:], upper[1:]))
        )

    # Partial lower subnet.
    elif lower[1:] == [0] * len(lower[1:]):
        return '%s|%s' % (
            ip_array_regex(lower, [upper[0] - 1] + [255] * len(upper[1:])),
            ip_array_regex([upper[0]] + [0] * len(upper[1:]), upper)
        )

    # Partial upper subnet.
    elif upper[1:] == [255] * len(upper[1:]):
        return '%s|%s' % (
            ip_array_regex(lower, [lower[0]] + [255] * len(lower[1:])),
            ip_array_regex([lower[0] + 1] + [0] * len(lower[1:]), upper)
        )

    # First octets just 1 apart.
    elif upper[0] - lower[0] == 1:
        return '%s|%s' % (
            ip_array_regex(lower, [lower[0]] + [255] * len(lower[1:])),
            ip_array_regex([upper[0]] + [0] * len(upper[1:]), upper)
        )

    # First octets more than 1 apart.
    else:
        return '%s|%s|%s' % (
            ip_array_regex(lower, [lower[0]] + [255] * len(lower[1:])),
            ip_array_regex([lower[0] + 1] + [0]   * len(lower[1:]),
                           [upper[0] - 1] + [255] * len(upper[1:])),
            ip_array_regex([upper[0]] + [0] * len(upper[1:]), upper)
        )

答案 2 :(得分:1)

如果它适用于Apache ......我没有尝试过,但它可能有效:

RewriteCond %{REMOTE_ADDR} !<147.63.23.156
RewriteCond %{REMOTE_ADDR} !>147.63.23.159

(两个连续RewriteCond由默认逻辑AND

连接

只需要注意不同位数的范围(例如95-105应分为95-99和100-105,因为它是字典排序)。

答案 3 :(得分:0)

我绝对同意评论者的观点,纯粹的正则表达式解决方案对于这里的工作来说是错误的工具。只需使用您已有的正则表达式来提取前缀,最小值和最大值

$prefix, $minimum, $maximum = match('(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})/(\d{1,3})', $line).groups()

然后针对${prefix}(\d+)

测试您的IP地址
$lastgroup = match($prefix + '(\d+)', $addr).groups()[0]

并比较最后一组,看它是否在适当的范围内,

return int($minimum) <= int($lastgroup) <= int($maximum)

代码示例是伪代码,当然 - 转换为您选择的语言。

答案 4 :(得分:0)

据我所知,这不能通过直接正则表达式完成,但还需要一些代码。例如,在PHP中,您可以使用以下内容:

function make_range($ip){
    $regex = '#(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})/(\d{1,3})#';
    if ( preg_match($regex, $ip, $matches) ){
        while($matches[1] <= $matches[2]){
            print "{$matches[0]}.{$matches[1]}";
            $matches[1]++;
        }
    } else {
        exit('not a supported IP range');
    } 
}

为了使用RewriteCond,我认为有些黑魔法会按顺序......

无论如何,这将如何与RewriteCond一起使用?你有几台服务器,想要快速制作.htaccess文件吗?如果是这样,那么只需将该函数添加到一个更大的脚本中,该脚本需要一些参数并删除.htaccess文件。