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

7 投票
3 回答
13788 浏览
提问于 2025-04-16 14:32

给定一个文本文件,其中我想匹配的字符被单引号包围,但可能有零个或一个被转义的单引号,还有零个或多个制表符和换行符(这些不需要转义)——我只想匹配文本部分。举个例子:

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()来获取每一行,这显然把传递给正则表达式的行分开了。我正在考虑重写这部分,但如果有任何建议也会很有帮助。

3 个回答

2

你可以试试这样做:

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

这个方法会从第一个找到的单引号开始匹配,直到遇到第一个没有被反斜杠(\)前面的单引号为止。在这两个单引号之间的换行符和制表符也会被包含在内。

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的"展开循环"效率技巧进行了优化。(详细信息请查看:《正则表达式精髓(第三版)》

需要注意的是,上面的正则表达式和下面这个是等价的(后者更常见,但在大多数NFA正则表达式实现中速度较慢):

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

4

这样做应该可以:

menu_item = '((?:[^'\\]|\\')*)'

这里的 (?:[^'\\]|\\')* 这部分是用来匹配任何字符的序列,除了 '\,或者一个字面上的 \'。前面的表达式 [^'\\] 也允许换行符和制表符,但你需要把它们替换成一个空格。

撰写回答