Python正则表达式匹配单引号中的文本,忽略转义的引号(以及制表符/换行符)

7

给定一个文本文件,其中我想匹配的字符由单引号分隔,但可能有零个或一个转义单引号,以及零个或多个制表符和换行符(未转义)- 我只想匹配文本。 例如:

menu_item = 'casserole';
menu_item = 'meat 
            loaf';
menu_item = 'Tony\'s magic pizza';
menu_item = 'hamburger';
menu_item = 'Dave\'s famous pizza';
menu_item = 'Dave\'s lesser-known
    gyro';

我想仅获取文本(包括空格),忽略制表符和换行符 - 实际上,我并不在意转义引号是否出现在结果中,只要它不影响匹配即可。
casserole
meat loaf
Tonys magic pizza
hamburger
Daves famous pizza
Dave\'s lesser-known gyro # quote is okay if necessary.

我已经成功创建了一个几乎可以实现的正则表达式 - 它处理了转义引号,但没有处理换行符:

menuPat = r"menu_item = \'(.*)(\\\')?(\t|\n)*(.*)\'"
for line in inFP.readlines():
    m = re.search(menuPat, line)
    if m is not None:
        print m.group()

有很多关于正则表达式的问题 - 但大多数都是使用Perl,如果有一个可以实现我想要的功能,我也无法找出来:)而且由于我使用的是Python,所以我不介意它是否跨越多个组,很容易将它们重新组合。
一些答案说只需要使用代码解析文本。虽然我相信我可以做到 - 但我离一个可用的正则表达式还很近:) 而且似乎应该可以做到。
更新:我刚才意识到我正在使用Python的readlines()方法获取每一行,这显然会导致传递给正则表达式的行被分割。我正在考虑重写它,但对于那部分的任何建议也将非常有帮助。

不要重复 - 我也试图处理(未转义的)换行符打乱我的输入数据。 - John C
1
我同意,但我觉得还是值得指出一下。只需使用re.MULTILINE(http://docs.python.org/library/re.html#re.MULTILINE)来匹配多行,使用`$`来忽略/匹配换行符,以及使用`\s`(同样的链接)来匹配空格。前进吧,小草蜢 ;) - phooji
1
作为一个注脚:字符串 menu_item = 'Dave\'s lesser-known \n gyro'; 并没有包含一个转义的单引号。这个字符串字面上包含了一个,但这只是为了帮助 Python 区分字符串结尾的引号。如果你想要真正包含转义单引号的测试用例,你需要像这样写:ohai = 'Dave\\\'s' - phooji
3个回答

14

这段经过测试的脚本应该能解决问题:

import re
re_sq_long = r"""
    # Match single quoted string with escaped stuff.
    '            # Opening literal quote
    (            # $1: Capture string contents
      [^'\\]*    # Zero or more non-', non-backslash
      (?:        # "unroll-the-loop"!
        \\.      # Allow escaped anything.
        [^'\\]*  # Zero or more non-', non-backslash
      )*         # Finish {(special normal*)*} construct.
    )            # End $1: String contents.
    '            # Closing literal quote
    """
re_sq_short = r"'([^'\\]*(?:\\.[^'\\]*)*)'"

data = r'''
        menu_item = 'casserole';
        menu_item = 'meat 
                    loaf';
        menu_item = 'Tony\'s magic pizza';
        menu_item = 'hamburger';
        menu_item = 'Dave\'s famous pizza';
        menu_item = 'Dave\'s lesser-known
            gyro';'''
matches = re.findall(re_sq_long, data, re.DOTALL | re.VERBOSE)
menu_items = []
for match in matches:
    match = re.sub('\s+', ' ', match) # Clean whitespace
    match = re.sub(r'\\', '', match)  # remove escapes
    menu_items.append(match)          # Add to menu list

print (menu_items)

以下是正则表达式的简化版本:

'([^'\\]*(?:\\.[^'\\]*)*)'

这个正则表达式使用了Jeffrey Friedl的"unrolling-the-loop"优化技术。(详见:Mastering Regular Expressions (第三版))

请注意,上述正则表达式等同于以下更常见但大多数NFA正则表达式实现速度较慢的表达式:

'((?:[^'\\]|\\.)*)'


4
这应该可以解决问题:
menu_item = '((?:[^'\\]|\\')*)'

这里的(?:[^'\\]|\\')*部分匹配除了'\或字面上的\'之外的任意字符序列。前一个表达式[^'\\]也允许换行和制表符,您需要将其替换为一个空格。

当你说“替换为单个空格”时,你是指在运行这个正则表达式之前清理/删除制表符/换行符吗?当我尝试使用你的正则表达式时,它没有匹配任何带有换行符的行。 - John C
@John C:不,我会用类似 re.sub(r"[\n\r\t]+", " ", match) 的东西来处理。 - Gumbo
然而,对于具有换行符的输入行,我的匹配变量m为空,因此没有任何内容可替换。 - John C
啊!看着我的代码,我正在对输入文件执行*readlines()*操作 - 我猜想这是在换行符上断开了。显然我需要重新编写更多的代码(叹气)。 - John C
值得注意的是,尽管此表达式适用于转义引号,但当遇到其他转义序列(如\n或单个反斜杠)时,它也不会起作用。您需要将预期遇到的所有转义序列添加到组中,例如:'((?:[^'\\]|\\|\\r|\\n|\\t|\\\\')*)' - Sammitch

2
您可以像这样尝试它:

pattern = re.compile(r"menu_item = '(.*?)(?<!\\)'", re.DOTALL)

它会从找到的第一个单引号开始匹配,并在第一个非反斜杠引导的单引号处结束。它还捕获两个单引号之间发现的任何换行符和制表符。


看起来很有趣,但正如我在另一个评论中指出的那样 - 我刚意识到我正在使用*readlines()*,它会在换行符上断开,所以我还有另一个问题要解决。 - John C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接