如何用正则表达式匹配特定长度的字符串

7 投票
5 回答
5565 浏览
提问于 2025-04-15 11:59

我在做一个项目,想实现BitTorrent协议中的一小部分,具体可以在这里找到。特别是,我想使用其中的“Bencoding”部分,这是一种安全地编码数据以便通过网络传输的方法。它的格式如下:

8:a string => "a string"
i1234e => 1234
l1:a1:be => ['a', 'b']
d1:a1:b3:one3:twoe => {'a':'b', 'one':two}

编码的部分相对简单,但解码就变得很麻烦了。例如,如果我有一个字符串列表,我就没有办法把它们分开成单独的字符串。我尝试了几种不同的解决方案,包括PyParsing和一个自定义的标记解析器。目前我在尝试使用正则表达式,感觉还不错,但在处理字符串的问题上还是卡住了。我的当前正则表达式是:

(?P<length>\d+):(?P<contents>.{\1})

不过,我似乎无法把第一个组的长度用作第二个组的长度。有没有什么好的方法可以做到这一点?还是说我在这个问题上走错了方向,答案就在我面前?

5 个回答

2

你可以把这个过程分成两个步骤来做。其实,用正则表达式来处理这么简单的解析问题有点过于复杂了。下面是我会怎么做的:

def read_string(stream):
    pos = stream.index(':')
    length = int(stream[0:pos])
    string = stream[pos+1:pos+1+length]
    return string, stream[pos+1+length:]

这是一种函数式的解析方式,它会返回解析出来的值和剩下的内容。

对于列表来说,可能会这样:

def read_list(stream):
    stream = stream[1:]
    result = []
    while stream[0] != 'e':
        obj, stream = read_object(stream)
        result.append(obj)
    stream = stream[1:]
    return result

然后你需要定义一个叫做 read_object 的函数,它会检查内容流的第一个字符,并根据这个字符来决定接下来的处理方式。

2

你可以通过解析字符串两次来实现这个功能。第一次使用正则表达式来获取字符串的长度。然后在第二次的正则表达式中,把这个长度加进去,形成一个有效的表达式。

我不太确定在Python中怎么做,但这里有一个C#的示例:

string regex = "^[A-Za-z0-9_]{1," + length + "}$"

这个正则表达式可以匹配1到指定长度的字符,这些字符可以是字母、数字或者下划线,而这个长度是通过之前的正则表达式获取的。

希望这对你有帮助 :)

10

你用来解析这些内容的工具需要能够记住状态,也就是说,它需要记住一些信息。而正则表达式通常不具备这种记忆能力,所以用它来处理这个问题并不合适。

如果你只需要处理这几种数据类型,我建议你为每种数据类型写一个专门的解析器。在读取第一个字符后,就把控制权交给相应的解析器。

其实我现在就想实现一个,但时间有点晚了。

好吧,我决定写一个实现:

from StringIO import StringIO
import string

inputs = ["10:a stringly",
         "i1234e" ,
         "l1:a1:be",
         "d1:a1:b3:one3:twoe"]

# Constants
DICT_TYPE = 'd'
LIST_TYPE = 'l'
INT_TYPE  = 'i'
TOKEN_EOF = ''
TOKEN_END = 'e'
COLON     = ':'


class BadTypeIndicatorException(Exception):pass


def read_int(stream):

   s = ""

   while True:
      ch = stream.read(1)
      if ch not in [TOKEN_EOF, TOKEN_END, COLON]:
         s += ch
      else:
         break

   return s


def tokenize(stream):

   s = ""

   while True:

      ch = stream.read(1)

      if ch == TOKEN_END or ch == TOKEN_EOF:
         return 

      if ch == COLON:
         length = int(s)
         yield stream.read(length)
         s = ""

      else:
         s += ch


def parse(stream):

   TYPE = stream.read(1)

   if TYPE in string.digits:
      length = int( TYPE + read_int(stream) )
      return stream.read(length)

   elif TYPE is INT_TYPE: 
      return int( read_int(stream) )

   elif TYPE is LIST_TYPE: 
      return list(tokenize(stream))

   elif TYPE is DICT_TYPE:
      tokens = list(tokenize(stream))
      return dict(zip(tokens[0::2], tokens[1::2]))

   else: 
      raise BadTypeIndicatorException



for input in inputs:
   stream = StringIO(input)
   print parse(stream)

撰写回答