如何使用正则表达式处理这样的字符串?
我想创建一个正则表达式,用来匹配这样的字符串:
<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 个回答
理论上这是可能的,但你给自己制造了很多麻烦。这里有几个问题:
1) 你把空格当作分隔符,但路径名中也允许有空格。这会导致混淆。你可以通过强制应用程序使用没有空格的路径来避免这个问题。
2) 你在最后有两个可选参数。这意味着当你看到“C:\LogTo Path 256 300”时,你根本不知道这个路径是“C:\LogTo Path 256 300”没有可选参数,还是“C:\Log To Path 256”有一个可选参数,或者是“C:\LogTo Path”有两个可选参数。
这可以通过在输出上使用替换算法来轻松解决。比如把空格替换成下划线,把下划线替换成双下划线。这样在你把日志文件按空格分开后,就可以可靠地还原。
即使是人也无法100%可靠地完成这个功能。
如果你假设所有路径都以星号、反斜杠或.log结尾,你可以使用正向查找来找到路径的结尾,但如果没有一些规则来指导你,那就麻烦了。
我觉得用一个正则表达式来解决这个问题会太复杂,让任何试图维护代码的人都感到崩溃。我自己非常喜欢用正则表达式,尽可能多地使用,但我不会尝试这个。
问题在于,你允许文件名中有空格,同时又用空格来分隔不同的字段,这样就会造成混淆。你需要使用一个不同的分隔符,这个分隔符在文件名中不能出现,或者用其他方法来表示带有空格的文件名,比如把它们放在引号里。
单靠空格来分割是行不通的。不过,如果你对数据做一些假设,就可以让它正常工作。
我想到的一些假设包括:
SERVER
、SERVERKEY
和COMMAND
中不包含空格:\S+
FOLDERPATH
以斜杠开头:/.*?
RETENTION
是一个数字:\d+
TRANSFERMODE
中不包含空格:\S+
OUTPUTPATH
以驱动器开头并以斜杠结尾:[A-Z]:\\.*?\\
LOGTO
要么是“NO
”这个词,要么是以驱动器开头的路径:[A-Z]:\\.*?
MAXSIZE
和OFFSET
是数字:\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'}
>>>