<h2>单引号=r“<code>'[^'\\]*(?:\\.[^'\\]*)*'"</code></h2>
<p>首先要注意,MizardX的答案是100%准确的。我想补充一些关于效率的建议。其次,我想指出的是,这个问题在很久以前就已经得到了解决和优化—请参见:<a href="https://rads.stackoverflow.com/amzn/click/com/0596528124" rel="noreferrer" title="By Jeffrey Friedl. Best book on Regex - ever!">Mastering Regular Expressions (3rd Edition)</a>(它非常详细地介绍了这个特定的问题—<em>高度推荐的</em>)。</p>
<p>首先让我们看看子表达式,以匹配可能包含转义单引号的单引号字符串。如果你打算允许转义单引号,你最好至少也允许转义转义(这就是道格拉斯·利德的答案)。但只要你坚持下去,就很容易让别人逃脱。有这些要求。米扎德是唯一一个表达正确的人。这里它有短格式和长格式(我冒昧地用<code>VERBOSE</code>模式编写了这篇文章,其中有很多描述性注释——对于非平凡的正则表达式,您应该总是这样做:</p>
<pre class="lang-py prettyprint-override"><code># MizardX's correct regex to match single quoted string:
re_sq_short = r"'((?:\\.|[^\\'])*)'"
re_sq_long = r"""
' # Literal opening quote
( # Capture group $1: Contents.
(?: # Group for contents alternatives
\\. # Either escaped anything
| [^\\'] # or one non-quote, non-escape.
)* # Zero or more contents alternatives.
) # End $1: Contents.
'
"""
</code></pre>
<p>这可以工作,并正确匹配以下所有字符串测试用例:</p>
<pre class="lang-py prettyprint-override"><code>text01 = r"out1 'escaped-escape: \\ ' out2"
test02 = r"out1 'escaped-quote: \' ' out2"
test03 = r"out1 'escaped-anything: \X ' out2"
test04 = r"out1 'two escaped escapes: \\\\ ' out2"
test05 = r"out1 'escaped-quote at end: \'' out2"
test06 = r"out1 'escaped-escape at end: \\' out2"
</code></pre>
<p>好吧,现在让我们开始改进这个。首先,备选方案的顺序会有所不同,人们应该总是把最有可能的备选方案放在首位。在这种情况下,非转义字符比转义字符更可能出现,因此颠倒顺序将稍微提高regex的效率,如下所示:</p>
<pre class="lang-py prettyprint-override"><code># Better regex to match single quoted string:
re_sq_short = r"'((?:[^\\']|\\.)*)'"
re_sq_long = r"""
' # Literal opening quote
( # $1: Contents.
(?: # Group for contents alternatives
[^\\'] # Either a non-quote, non-escape,
| \\. # or an escaped anything.
)* # Zero or more contents alternatives.
) # End $1: Contents.
'
"""
</code></pre>
<h2>“展开循环”:</h2>
<p>这稍微好一点,但是可以通过应用Jeffrey Friedl的“展开循环”</em>效率技术(从{a2})进一步改进(显著)。上面的正则表达式不是最优的,因为它必须费心地将星量词应用于两个可选的非捕获组,每个可选的组一次只消耗一个或两个字符。这种交替可以通过认识到一个相似的模式反复出现而完全消除,并且可以构造一个等价的表达式来做相同的事情而无需交替。下面是一个优化表达式,用于匹配单引号字符串并将其内容捕获到组<code>$1</code>:</p>
<pre class="lang-py prettyprint-override"><code># Better regex to match single quoted string:
re_sq_short = r"'([^'\\]*(?:\\.[^'\\]*)*)'"
re_sq_long = r"""
' # Literal opening quote
( # $1: Contents.
[^'\\]* # {normal*} Zero or more non-', non-escapes.
(?: # Group for {(special normal*)*} construct.
\\. # {special} Escaped anything.
[^'\\]* # More {normal*}.
)* # Finish up {(special normal*)*} construct.
) # End $1: Contents.
'
"""
</code></pre>
<p>这个表达式将所有非引号、非反斜杠(大多数字符串的绝大多数)一饮而尽,这大大减少了regex引擎必须执行的工作量。你问得好多少?好吧,我把这个问题中出现的每个正则表达式都输入到<a href="http://www.regexbuddy.com/" rel="noreferrer" title="An excellent tool for crafting and debugging regular expressions">RegexBuddy</a>中,并测量正则表达式引擎完成以下字符串匹配(所有解决方案都正确匹配)所需的步骤:</p>
<p><code>'This is an example string which contains one \'internally quoted\' string.'</code></p>
<p>以下是上述测试字符串的基准测试结果:</p>
<pre class="lang-py prettyprint-override"><code>r"""
AUTHOR SINGLE-QUOTE REGEX STEPS TO: MATCH NON-MATCH
Evan Fosmark '(.*?)(?<!\\)' 374 376
Douglas Leeder '(([^\\']|\\'|\\\\)*)' 154 444
cletus/PEZ '((?:\\'|[^'])*)(?<!\\)' 223 527
MizardX '((?:\\.|[^\\'])*)' 221 369
MizardX(improved) '((?:[^\\']|\\.)*)' 153 369
Jeffrey Friedl '([^\\']*(?:\\.[^\\']*)*)' 13 19
"""
</code></pre>
<p>这些步骤是使用RegexBuddy调试器函数匹配测试字符串所需的步骤数。“NON-MATCH”列是从测试字符串中移除右引号时声明匹配失败所需的步骤数。如您所见,对于匹配和不匹配的情况,差异都是显著的。还请注意,这些效率改进仅适用于使用回溯的NFA引擎(即Perl、PHP、Java、Python、Javascript、.NET、Ruby和大多数其他引擎)。DFA引擎将看不到这种技术的任何性能提升(请参见:<a href="http://swtch.com/~rsc/regexp/regexp1.html" rel="noreferrer">Regular Expression Matching Can Be Simple And Fast</a>)。</p>
<h2>关于完整的解决方案:</h2>
<p>原始问题(我的解释)的目标是从较大的字符串中挑选单引号的子字符串(可能包含转义引号)。如果知道被引用子字符串之外的文本永远不会包含转义单引号,则上面的正则表达式将执行此操作。然而,要正确地匹配文本海洋中的单引号子字符串,该字符串与转义引号、转义转义符和转义任何else(这是我对作者追求的东西的解释),<strike>需要从字符串的开头进行解析</strike>否(这是我最初的想法),但它不是-这可以通过使用MizardX非常聪明的<code>(?<!\\)(?:\\\\)*</code>表达式来实现。下面是一些测试字符串,用于练习各种解决方案:</p>
<pre class="lang-py prettyprint-override"><code>text01 = r"out1 'escaped-escape: \\ ' out2"
test02 = r"out1 'escaped-quote: \' ' out2"
test03 = r"out1 'escaped-anything: \X ' out2"
test04 = r"out1 'two escaped escapes: \\\\ ' out2"
test05 = r"out1 'escaped-quote at end: \'' out2"
test06 = r"out1 'escaped-escape at end: \\' out2"
test07 = r"out1 'str1' out2 'str2' out2"
test08 = r"out1 \' 'str1' out2 'str2' out2"
test09 = r"out1 \\\' 'str1' out2 'str2' out2"
test10 = r"out1 \\ 'str1' out2 'str2' out2"
test11 = r"out1 \\\\ 'str1' out2 'str2' out2"
test12 = r"out1 \\'str1' out2 'str2' out2"
test13 = r"out1 \\\\'str1' out2 'str2' out2"
test14 = r"out1 'str1''str2''str3' out2"
</code></pre>
<p>给定这个测试数据,让我们看看各种解决方案的运行情况('p'==通过,'XX'==失败):</p>
<pre class="lang-py prettyprint-override"><code>r"""
AUTHOR/REGEX 01 02 03 04 05 06 07 08 09 10 11 12 13 14
Douglas Leeder p p XX p p p p p p p p XX XX XX
r"(?:^|[^\\])'(([^\\']|\\'|\\\\)*)'"
cletus/PEZ p p p p p XX p p p p p XX XX XX
r"(?<!\\)'((?:\\'|[^'])*)(?<!\\)'"
MizardX p p p p p p p p p p p p p p
r"(?<!\\)(?:\\\\)*'((?:\\.|[^\\'])*)'"
ridgerunner p p p p p p p p p p p p p p
r"(?<!\\)(?:\\\\)*'([^'\\]*(?:\\.[^'\\]*)*)'"
"""
</code></pre>
<h2>工作测试脚本:</h2>
<pre class="lang-py prettyprint-override"><code>import re
data_list = [
r"out1 'escaped-escape: \\ ' out2",
r"out1 'escaped-quote: \' ' out2",
r"out1 'escaped-anything: \X ' out2",
r"out1 'two escaped escapes: \\\\ ' out2",
r"out1 'escaped-quote at end: \'' out2",
r"out1 'escaped-escape at end: \\' out2",
r"out1 'str1' out2 'str2' out2",
r"out1 \' 'str1' out2 'str2' out2",
r"out1 \\\' 'str1' out2 'str2' out2",
r"out1 \\ 'str1' out2 'str2' out2",
r"out1 \\\\ 'str1' out2 'str2' out2",
r"out1 \\'str1' out2 'str2' out2",
r"out1 \\\\'str1' out2 'str2' out2",
r"out1 'str1''str2''str3' out2",
]
regex = re.compile(
r"""(?<!\\)(?:\\\\)*'([^'\\]*(?:\\.[^'\\]*)*)'""",
re.DOTALL)
data_cnt = 0
for data in data_list:
data_cnt += 1
print ("\nData string %d" % (data_cnt))
m_cnt = 0
for match in regex.finditer(data):
m_cnt += 1
if (match.group(1)):
print(" quoted sub-string%3d = \"%s\"" %
(m_cnt, match.group(1)))
</code></pre>
<h2>呸!</h2>
<p>p.s.感谢MizardX非常酷的<code>(?<!\\)(?:\\\\)*</code>表达式。每天都学点新东西!</p>