解析带注释的配置文件的正则表达式
编辑:我只是好奇怎么让这个正则表达式工作。请不要告诉我有更简单的方法,这显而易见!:P
我正在用Python写一个正则表达式,目的是解析配置文件中的每一行。每一行可能看起来像这样:
someoption1 = some value # some comment
# this line is only a comment
someoption2 = some value with an escaped \# hash
someoption3 = some value with a \# hash # some comment
我的想法是,井号(#)后面的内容都被视为注释,除非这个井号前面有一个反斜杠(\)进行转义。
我想用正则表达式把每一行拆分成几个部分:前面的空白字符、赋值的左边、赋值的右边和注释。以示例中的第一行为例,拆分结果应该是:
- 空白字符:""
- 赋值左边:"someoption1 ="
- 赋值右边:" some value "
- 注释:"# some comment"
这是我目前写的正则表达式:
^(\s)?(\S+\s?=)?(([^\#]*(\\\#)*)*)?(\#.*)?$
我对正则表达式很糟糕,所以请随意批评!
使用Python的re.findAll()
,返回的结果是:
- 第0个索引:空白字符,没问题
- 第1个索引:赋值的左边
- 第2个索引:赋值的右边,直到第一个井号,不管有没有转义(这是不对的)
- 第5个索引:第一个井号,不管有没有转义,以及它后面的所有内容(这是不对的)
可能我对正则表达式的一些基本概念还不太明白。如果有人能解决这个问题,我将永远感激不尽……
6 个回答
到目前为止,提出的5个解决方案中,只有Gumbo的方案真正有效。这里是我的解决方案,它也能正常工作,并且有很多注释:
import re
def fn(line):
match = re.search(
r"""^ # Anchor to start of line
(\s*) # $1: Zero or more leading ws chars
(?: # Begin group for optional var=value.
(\S+) # $2: Variable name. One or more non-spaces.
(\s*=\s*) # $3: Assignment operator, optional ws
( # $4: Everything up to comment or EOL.
[^#\\]* # Unrolling the loop 1st normal*.
(?: # Begin (special normal*)* construct.
\\. # special is backslash-anything.
[^#\\]* # More normal*.
)* # End (special normal*)* construct.
) # End $4: Value.
)? # End group for optional var=value.
((?:\#.*)?) # $5: Optional comment.
$ # Anchor to end of line""",
line, re.MULTILINE | re.VERBOSE)
return match.groups()
print (fn(r" # just a comment"))
print (fn(r" option1 = value"))
print (fn(r" option2 = value # no escape == IS a comment"))
print (fn(r" option3 = value \# 1 escape == NOT a comment"))
print (fn(r" option4 = value \\# 2 escapes == IS a comment"))
print (fn(r" option5 = value \\\# 3 escapes == NOT a comment"))
print (fn(r" option6 = value \\\\# 4 escapes == IS a comment"))
上面的脚本产生了以下(正确的)输出:(在Python 3.0.1中测试过)
(' ', None, None, None, '# just a comment')
(' ', 'option1', ' = ', 'value', '')
(' ', 'option2', ' = ', 'value ', '# no escape == IS a comment')
(' ', 'option3', ' = ', 'value \\# 1 escape == NOT a comment', '')
(' ', 'option4', ' = ', 'value \\\\', '# 2 escapes == IS a comment')
(' ', 'option5', ' = ', 'value \\\\\\# 3 escapes == NOT a comment', '')
(' ', 'option6', ' = ', 'value \\\\\\\\', '# 4 escapes == IS a comment')
请注意,这个解决方案使用了Jeffrey Friedl的“循环展开效率技术(可以消除慢速交替)”。它完全不使用回顾查找,并且速度非常快。《正则表达式精髓(第三版)》是任何声称“懂得”正则表达式的人都必须阅读的书。(当我说“懂得”时,我是指那种Neo的“我会功夫!”的感觉 :)
我会在多行模式下使用这个正则表达式:
^\s*([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*((?:[^\\#]|\\.)+)
这样可以让任何字符都可以被转义(比如用\\.
)。如果你只想允许#
这个字符,可以用\\#
来代替。
你的正则表达式没有按你想的那样匹配,是因为正则表达式的贪婪匹配特性:每个部分会尽量匹配最长的子串,以便剩下的字符串还能用正则表达式的其他部分匹配。
以你某一行中带有转义字符#为例,具体情况是:
[^\#]*
(顺便说一下,其实不需要转义#)会匹配第一个#之前的所有内容,包括它前面的反斜杠(\\\#)*
不会匹配任何东西,因为此时字符串是以#开头的- 最后的
(\#.*)
会匹配字符串的其余部分
这里有个简单的例子来强调这种可能让人困惑的行为:在正则表达式(a*)(ab)?(b*)
中,(ab)?
永远不会匹配到任何东西。
我认为这个正则表达式(基于原来的那个)应该可以工作:^\s*(\S+\s*=([^\\#]|\\#?)*)?(#.*)?$