2017-03-31 96 views
4

正則表達式:如何簡化這個正則表達式以避免遞歸?

(?|`(?>[^`\\]|\\.|``)*`|'(?>[^'\\]|\\.|'')*'|"(?>[^"\\]|\\.|"")*"|(\?{1,2})|(:{1,2})([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)) 

示例輸入:

INSERT INTO xyz WHERE 
a=? 
and b="what?" 
and ??="cheese" 
and `col?`='OK' 
and ::col='another' 
and last!=:least 

https://regex101.com/r/HnTVXx/6

應該匹配???:xyz::xyz但如果它們是反引號串,雙內帶引號的字符串或單引號字符串。

當我嘗試在PHP中以非常大的輸入運行此操作時,我從preg_last_error()得到PREG_RECURSION_LIMIT_ERROR

我怎樣才能簡化這個正則表達式模式,使它不會做這麼多的遞歸?


下面是一些測試代碼顯示使用Niet's optimized regex在PHP錯誤:https://3v4l.org/GdtmP錯誤代碼6 PREG_JIT_STACKLIMIT_ERROR。另一位我所看到的是3 = PREG_RECURSION_LIMIT_ERROR

+0

@anubhava:?不,這將匹配一個'''中和b = 「世界衛生大會\」 T「' –

+1

OP的正則表達式[手柄轉義引號](https://regex101.com/r/3CuYWG/1)。 –

+2

@JakubSzumiato哈哈...如果我不使用正則表達式,我必須解析PHP中的整個SQL查詢。但是現有的庫在一些邊緣情況下失敗) – mpen

回答

1

同樣的想法跳過QUOT ED部分((*SKIP)(*F)組合),而且還2項技術,以減少正則表達式引擎的工作:

  • 的第一個字符識別
  • 展開的模式

這兩個技術有一個共同點:限制改變的代價。

當您的圖案以變化開始時,第一個字符歧視很有用。開始時的交替問題是每個分支都要進行測試,以確定模式失敗的位置。由於大多數時候,一個字符串中有很多失敗的位置,很快拋棄它們是一個顯着的改進。

舉例來說,像這樣:"...|'...|`...|:...也可以寫成這樣:

(?=["'`:])(?:"...|'...|`...|:...) 

["'`:](?:(?<=")...|(?<=')...|(?<=`)...|(?<=:)...) 

這樣,每個不使用這些字符開始位置["'`:]是立即拒絕第一個標記而不測試每個分支。


鋪開模式由改寫這樣的:" (?:[^"\\]|\\.)* "到:

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

注意,這樣的設計消除了交替和減少步數急劇:
basic
unrolled


使用這兩種技巧,您的模式可以被寫入ñ這樣的:

~ 
[`'"?:] 
(?: 
    (?<=`) [^`\\]*+ (?s:\\.[^`\\]*|``[^`\\]*)*+ ` (*SKIP) (*F) 
    | 
    (?<=') [^'\\]*+ (?s:\\.[^'\\]*|''[^'\\]*)*+ ' (*SKIP) (*F) 
    | 
    (?<=") [^"\\]*+ (?s:\\.[^"\\]*|""[^"\\]*)*+ " (*SKIP) (*F) 
    | 
    (?<=\?) \?? 
    | 
    (?<=:) :? ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*) 
) 
~x 

demo


其他方式:不是在一開始使用的交替(改善或沒有),您可以構建所有連續的結果字符串相匹配的模式。一般設計是:

\G (all I don't want) (*SKIP) \K (what I am looking for) 

\G是,無論是先前的結果後的位置或字符串的開頭相匹配的錨。用它開始模式可確保所有匹配都是連續的。在這種情況下(在模式的開始和整個模式的因素中),您也可以用A修飾符替換它。

這給:

~ 
[^`'"?:]* 
(?: 
    ` [^`\\]*+ (?s:\\.[^`\\]*|``[^`\\]*)*+ ` [^`'"?:]* 
    | 
    ' [^'\\]*+ (?s:\\.[^'\\]*|''[^'\\]*)*+ ' [^`'"?:]* 
    | 
    " [^"\\]*+ (?s:\\.[^"\\]*|""[^"\\]*)*+ " [^`'"?:]* 
)* 
\K # only the part of the match after this position is returned 
(*SKIP) # if the next subpattern fails, the contiguity is broken at this position 
(?: 
    \?{1,2} 
    | 
    :{1,2} ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*) 
) 
~Ax 

demo

+0

太棒了!我也必須這樣做。應該像Niet一樣,在引用反向引用的情況下正常工作,對吧?這使得正則表達式更清潔一些。 – mpen

+1

@mpen:引用反向引用是個不錯的主意。如果它縮短了圖案,它也會增加步數。如果你使用它,你不能使用展開的子模式技術。 –

+0

我想我必須將複選標記轉移給您 - 看起來像您的效率更高!根據regex101大約一半的步驟數。今天學到很多,謝謝。 – mpen

3

的「匹配這個東西,但不是在這種情況下」可以用這種模式實現的總體思路:

(don't match this(*SKIP)(*FAIL)|match this) 

在你的情況,你想要的東西像...

(
    (['"`]) # capture this quote character 
    (?:\\.|(?!\1).)*+ # any escaped character, or 
        # any character that isn't the captured one 
    \1  # the captured quote again 
    (*SKIP)(*FAIL) # ignore this 
    | 
    \?\?? # one or two question marks 
    | 
    ::?\w+ # word characters marked with one or two colons 
)x 

https://regex101.com/r/HnTVXx/7

+0

[不是它與'b =「wha \」t?'''中的''不匹配](https://regex101.com/r/HnTVXx/4) – anubhava

+0

這很漂亮。我不知道'(* SKIP)(* FAIL)'。儘管如此,我認爲我仍然遇到了PHP的限制。我會在一秒鐘後發佈一些代碼。 – mpen

+0

這裏:https://3v4l.org/GdtmP主題可能只是太大 - 你的正則表達式看起來非常高效(謝謝你!) – mpen