如何使用正则表达式处理这样的字符串?

0 投票
5 回答
494 浏览
提问于 2025-04-11 19:56

我想创建一个正则表达式,用来匹配这样的字符串:

<SERVER> <SERVERKEY> <COMMAND> <FOLDERPATH> <RETENTION> <TRANSFERMODE> <OUTPUTPATH> <LOGTO> <OPTIONAL-MAXSIZE> <OPTIONAL-OFFSET>

这些字段大多数只是简单的单词,但有些可以是路径,比如 FOLDERPATH 和 OUTPUTPATH,这些路径也可以是带有文件名和通配符的路径。

保留时间是一个数字,传输模式可以是 bin 或 ascii。问题在于,LOGTO 这个字段可以是一个带有日志文件名的路径,也可以是 NO,表示没有日志文件。

主要的问题是可选参数,它们都是数字,OFFSET 不能单独存在,必须有 MAXSIZE,但 MAXSIZE 可以没有 OFFSET。

这里有一些例子:

loveserver love copy /muffin* 20 bin C:\Puppies\ NO 256 300
loveserver love copy /muffin* 20 bin C:\Puppies\ NO 256
loveserver love copy /hats* 300 ascii C:\Puppies\no\ C:\log\love.log 256

现在主要的问题是,路径中可能会有空格,所以如果我用 . 来匹配所有内容,正则表达式在解析可选参数时就会出错,导致日志目标和输出路径连在一起。

而且如果我用 . 并开始去掉其中的一部分,正则表达式就会把东西放到不该放的地方。

这是我的正则表达式:

^(\s+)?(?P<SRCHOST>.+)(\s+)(?P<SRCKEY>.+)(\s+)(?P<COMMAND>COPY)(\s+)(?P<SRCDIR>.+)(\s+)(?P<RETENTION>\d+)(\s+)(?P<TRANSFER_MODE>BIN|ASC|BINARY|ASCII)(\s+)(?P<DSTDIR>.+)(\s+)(?P<LOGFILE>.+)(\s+)?(?P<SIZE>\d+)?(\s+)?(?P<OFFSET>\d+)?$

5 个回答

3

理论上这是可能的,但你给自己制造了很多麻烦。这里有几个问题:

1) 你把空格当作分隔符,但路径名中也允许有空格。这会导致混淆。你可以通过强制应用程序使用没有空格的路径来避免这个问题。

2) 你在最后有两个可选参数。这意味着当你看到“C:\LogTo Path 256 300”时,你根本不知道这个路径是“C:\LogTo Path 256 300”没有可选参数,还是“C:\Log To Path 256”有一个可选参数,或者是“C:\LogTo Path”有两个可选参数。

这可以通过在输出上使用替换算法来轻松解决。比如把空格替换成下划线,把下划线替换成双下划线。这样在你把日志文件按空格分开后,就可以可靠地还原。

即使是人也无法100%可靠地完成这个功能。

如果你假设所有路径都以星号、反斜杠或.log结尾,你可以使用正向查找来找到路径的结尾,但如果没有一些规则来指导你,那就麻烦了。

我觉得用一个正则表达式来解决这个问题会太复杂,让任何试图维护代码的人都感到崩溃。我自己非常喜欢用正则表达式,尽可能多地使用,但我不会尝试这个。

4

问题在于,你允许文件名中有空格,同时又用空格来分隔不同的字段,这样就会造成混淆。你需要使用一个不同的分隔符,这个分隔符在文件名中不能出现,或者用其他方法来表示带有空格的文件名,比如把它们放在引号里。

1

单靠空格来分割是行不通的。不过,如果你对数据做一些假设,就可以让它正常工作。

我想到的一些假设包括:

  • SERVERSERVERKEYCOMMAND中不包含空格:\S+
  • FOLDERPATH以斜杠开头:/.*?
  • RETENTION是一个数字:\d+
  • TRANSFERMODE中不包含空格:\S+
  • OUTPUTPATH以驱动器开头并以斜杠结尾:[A-Z]:\\.*?\\
  • LOGTO要么是“NO”这个词,要么是以驱动器开头的路径:[A-Z]:\\.*?
  • MAXSIZEOFFSET是数字:\d+

把这些都放在一起:

^\s*
(?P<SERVER>\S+)\s+
(?P<SERVERKEY>\S+)\s+
(?P<COMMAND>\S+)\s+
(?P<FOLDERPATH>/.*?)\s+   # Slash not that important, but should start with non-whitespace
(?P<RETENTION>\d+)\s+
(?P<TRANSFERMODE>\S+)\s+
(?P<OUTPUTPATH>[A-Z]:\\.*?\\)\s+   # Could also support network paths
(?P<LOGTO>NO|[A-Z]:\\.*?)
(?:
  \s+(?P<MAXSIZE>\d+)
  (?:
    \s+(?P<OFFSET>\d+)
  )?
)?
\s*$

一行代码:

^\s*(?P<SERVER>\S+)\s+(?P<SERVERKEY>\S+)\s+(?P<COMMAND>\S+)\s+(?P<FOLDERPATH>/.*?)\s+(?P<RETENTION>\d+)\s+(?P<TRANSFERMODE>\S+)\s+(?P<OUTPUTPATH>[A-Z]:\\.*?\\)\s+(?P<LOGTO>NO|[A-Z]:\\.*?)(?:\s+(?P<MAXSIZE>\d+)(?:\s+(?P<OFFSET>\d+))?)?\s*$

测试:

>>> import re
>>> p = re.compile(r'^(?P<SERVER>\S+)\s+(?P<SERVERKEY>\S+)\s+(?P<COMMAND>\S+)\s+(?P<FOLDERPATH>/.*?)\s+(?P<RETENTION>\d+)\s+(?P<TRANSFERMODE>\S+)\s+(?P<OUTPUTPATH>[A-Z]:\\.*?\\)\s+(?P<LOGTO>NO|[A-Z]:\\.*?)(?:\s+(?P<MAXSIZE>\d+)(?:\s+(?P<OFFSET>\d+))?)?\s*$',re.M)
>>> data = r"""loveserver love copy /muffin* 20 bin C:\Puppies\ NO 256 300
... loveserver love copy /muffin* 20 bin C:\Puppies\ NO 256
... loveserver love copy /hats* 300 ascii C:\Puppies\no\ C:\log\love.log 256"""
>>> import pprint
>>> for match in p.finditer(data):
...   print pprint.pprint(match.groupdict())
...
{'COMMAND': 'copy',
 'FOLDERPATH': '/muffin*',
 'LOGTO': 'NO',
 'MAXSIZE': '256',
 'OFFSET': '300',
 'OUTPUTPATH': 'C:\\Puppies\\',
 'RETENTION': '20',
 'SERVER': 'loveserver',
 'SERVERKEY': 'love',
 'TRANSFERMODE': 'bin'}
{'COMMAND': 'copy',
 'FOLDERPATH': '/muffin*',
 'LOGTO': 'NO',
 'MAXSIZE': '256',
 'OFFSET': None,
 'OUTPUTPATH': 'C:\\Puppies\\',
 'RETENTION': '20',
 'SERVER': 'loveserver',
 'SERVERKEY': 'love',
 'TRANSFERMODE': 'bin'}
{'COMMAND': 'copy',
 'FOLDERPATH': '/hats*',
 'LOGTO': 'C:\\log\\love.log',
 'MAXSIZE': '256',
 'OFFSET': None,
 'OUTPUTPATH': 'C:\\Puppies\\no\\',
 'RETENTION': '300',
 'SERVER': 'loveserver',
 'SERVERKEY': 'love',
 'TRANSFERMODE': 'ascii'}
>>>

撰写回答