编辑:我真的很好奇我是如何让这个正则表达式工作的。请不要告诉我有更简单的方法。这很明显! :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
这个想法是哈希符号之后的任何内容都被认为是注释,除非哈希是用斜杠转义的。
我正在尝试使用正则表达式将每一行分成单独的部分:前导空格,作业左侧,作业右侧和注释。对于示例中的第一行,细分将为:
这是我到目前为止的正则表达式:
^(\s)?(\S+\s?=)?(([^\#]*(\\\#)*)*)?(\#.*)?$
我对正则表达式很可怕,所以请随意拆开!
使用Python的re.findAll()
,这将返回:
我可能缺少一些关于正则表达式的基础知识。如果有人能解决这个问题,我将永远感激......
答案 0 :(得分:2)
你的正则表达式不匹配的原因是因为正则表达式的贪婪匹配行为:每个部分将匹配最长的子字符串,这样字符串的其余部分仍然可以与常规字符串的其余部分匹配表达
对于你的某条带有转义#的行,这意味着什么:
[^\#]*
(没有必要转义#btw)将匹配第一个哈希之前的所有内容,包括之前的反斜杠 (\\\#)*
将无法匹配任何内容,因为此时的字符串以#(\#.*)
将匹配字符串的其余部分一个简单的例子来强调这种可能不直观的行为:在正则表达式(a*)(ab)?(b*)
中,(ab)?
永远不会匹配任何内容
我相信这个正则表达式(基于原始表达式)应该有效:^\s*(\S+\s*=([^\\#]|\\#?)*)?(#.*)?$
答案 1 :(得分:2)
我会在多行模式下使用这个正则表达式:
^\s*([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*((?:[^\\#]|\\.)+)
这允许转义任何字符(\\.
)。如果您只想允许#
,请改用\\#
。
答案 2 :(得分:2)
到目前为止提供的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的“展开循环效率技术(消除了慢速交替)。它根本不使用任何环视,速度非常快。Mastering Regular Expressions (3rd edition)是必须读取对于任何声称“知道”正则表达的人。(当我说“知道”时,我的意思是在Neo“我知道Kung-Fu!”感觉:))
答案 3 :(得分:1)
我已经就这个问题的目的留下了评论,但是假设这个问题纯粹是关于正则表达式,我仍然会给出答案。
假设你一次只处理输入一行,我会把它作为一个两遍阶段。这意味着你将有2个正则表达式。
(.*?(?<!\\))#(.*)
的某些内容:首先分开#
而不是\
(请参阅有关负面反馈的文档); 答案 4 :(得分:0)
我根本不会使用正则表达式,因为我不会尝试使用热核弹头杀死苍蝇。
假设你一次只读一行,只需:
#
,请对整行设置注释并清空该行。#
之后不会立即找到\
的第一个匹配项,将注释设置为加上该行的其余部分,并将该行设置为之前的所有内容。\#
#
的所有匹配项
就是这样,你现在有一个正确的行和评论部分。使用正则表达式一定分割新的行部分。
例如:
import re
def fn(line):
# Split line into non-comment and comment.
comment = ""
if line[0] == "#":
comment = line
line = ""
else:
idx = re.search (r"[^\\]#", line)
if idx != None:
comment = line[idx.start()+1:]
line = line[:idx.start()+1]
# Split non-comment into key and value.
idx = re.search (r"=", line)
if idx == None:
key = line
val = ""
else:
key = line[:idx.start()]
val = line[idx.start()+1:]
val = val.replace ("\\#", "#")
return (key.strip(),val.strip(),comment.strip())
print fn(r"someoption1 = some value # some comment")
print fn(r"# this line is only a comment")
print fn(r"someoption2 = some value with an escaped \# hash")
print fn(r"someoption3 = some value with a \# hash # some comment")
产生
('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')
如果您必须使用正则表达式(违反我的建议),您的具体问题就在于:
[^\#]
这(假设您的意思是正确转义r"[^\\#]"
)将尝试匹配除\
或#
以外的任何字符,而不是您希望的序列\#
。你可以使用负面的后台来做这件事,但我总是这么说,一旦正规表达式变得匆匆对白痴不可读,最好还原到程序代码: - )
在反思中,更好的方法是使用多级分割(因此正则表达式不必因处理丢失的字段而变得太可怕),如下所示:
def fn(line):
line = line.strip() # remove spaces
first = re.split (r"\s*(?<!\\)#\s*", line, 1) # get non-comment/comment
if len(first) == 1: first.append ("") # ensure we have a comment
first[0] = first[0].replace("\\#","#") # unescape non-comment
second = re.split (r"\s*=\s*", first[0], 1) # get key and value
if len(second) == 1: second.append ("") # ensure we have a value
second.append (first[1]) # create 3-tuple
return second # and return it
这使用负前瞻来正确匹配注释分隔符,然后将非注释位分隔为键和值。空间也在这个空间中正确处理,产生:
['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']
答案 5 :(得分:0)
尝试将其分解为两个步骤:
转发处理以识别真实评论(首先#前面没有\(提示:“负面反馈”)),删除真实评论,然后将r"\#"
替换为"#"
处理无评论的余数。
BIG HINT:使用re.VERBOSE和评论