2014-03-01 40 views
1

我期待匹配正則表達式和負面字符(TCL)的組合。正則表達式找到正面和負面匹配

假設我想匹配包含'def'且不包含'hij'的行。

ab def hhh -> print 
abdefxxhijzz -> no print 
hij   -> no print 
123defhijxyz -> no print 
0def123hijxyz -> no print 

我曾嘗試:

{(def)(?!hij)} 
{(def).*(?!hij)} 
{.*(def).*(?!hij)} 
{.*(def).*(?!hij).*} 

所有錯誤打印 '0def123hijxyz'。

在cmd行我可以用2 x grep cmds來做到這一點。

echo 0def123hijxyz | grep def | grep -v hij 

難道你們的專家能幫助一個正則表達式來實現這個目標嗎?

謝謝, Gert。

+1

負面的正則表達式總是很棘手,因爲「缺少這個字符串」並不是真的可以「找到」的東西。你最好的選擇(我不知道怎麼寫TCL)相當於'grep -v' - 顛倒了整個過濾器,而不是正則表達式。 – IMSoP

回答

2

此正則表達式應該工作:

(?!.*hij)(.*def.*) 

它看起來走在了前面子.*hij,如果一個不能被發現,它匹配(.*def.*)

+0

這可能需要調整,具體取決於OP是否希望匹配整個單詞或子字符串。 – aliteralmind

0

我認爲這兩個任務,和我不根本不需要看正則表達式。

首先搜索包含所需字符串(「def」)的字符串,然後僅當字符串傳遞第一個測試時,驗證它不包含禁用字符串(「hij」)。

根據哪一種更可能消除最多的可能性,將其作爲第一步。例如,如果更多的字符串包含禁止的字符串的可能性更大,請首先檢查,因爲您的代碼效率更高。

2

您已經很近了,但您需要首先執行負向預覽,然後將其固定,以確保它僅在字符串的開始處應用一次。

{(?n)^(?!.*hij).*def.*} 
  • (?n)打開-line模式,允許^來匹配一行(大多數正則表達式的口味叫multiline模式)的開始。

  • (?!.*hij)在整個字符串中搜索hij,並在找到它時報告失敗。

  • .*def.*消耗整個字符串,如果它包含def

錨是必要的,以防止它匹配,其中有害字之前想要的一個,像hij def的字符串。如果沒有錨點,可以從i開始匹配。

2

對於這種檢查的,我寧可不使用正則表達式和而使用string:

if {[string match *def* "0def123hijxyz"] && ![string match *hij* "0def123hijxyz"]} { 
    puts "Match!" 
} 

string match使用水珠匹配,從而使*是通配符。

[string match *def* "0def123hijxyz"]如果def位於字符串內,則返回1,否則返回0。


如果你仍然對正則表達式的方法堅持,我建議這個表達式:

^(?!.*hij).*def 

^是線錨的開始導致正則表達式來檢查比賽只有一次,不重複當比賽失敗時(即在發現有hij或沒有def之後)。

(?!.*hij)中添加.*可以檢查整個字符串,而不是字符串中的單個位置。

.*def然後嘗試匹配def。除非需要更多匹配,否則不必使用另一個.*,例如,def後跟隨g,即使其間的其他字符是.*def.*g。最後使用.*只會爲正則表達式提供更多的工作。


一些基準測試...

% proc match {} { 
     if {[string match *def* "0def123hijxyz"] && ![string match *hij* "0def12 
3hijxyz"]} { 
     } 
} 
% proc regmatch {} { 
     if {[regexp -- {^(?!.*hij).*def} 0def123hijxyz]} { 
     } 
} 
% puts [time match 100000] 
0.49533 microseconds per iteration 
% puts [time regmatch 100000] 
1.38854 microseconds per iteration 
% proc regmatcher {} { 
     if {[regexp -- {(?n)^(?!.*hij).*def.*} 0def123hijxyz]} { 
     } 
} 
% puts [time regmatcher 100000] 
2.23913 microseconds per iteration 

regexp需要2-4倍,比簡單的字符串方法更長。

1

當測試這樣的事情,它有助於使一個小測試程序:

proc check {re} { 
    foreach s {"ab def hhh" "abdefxxhijzz" "hij" "123defhijxyz" "0def123hijxyz"} { 
     puts "$s => [regexp $re $s]" 
    } 
} 

讓我們來看看......

% check {(def)(?!hij)} 
ab def hhh => 1 
abdefxxhijzz => 1 
hij => 0 
123defhijxyz => 0 
0def123hijxyz => 1 
% check {.*(def).*(?!hij).*} 
ab def hhh => 1 
abdefxxhijzz => 1 
hij => 0 
123defhijxyz => 1 
0def123hijxyz => 1 

太好了!現在我們可以試用任何我們可能會想到的與所有測試用例相對應的RE。在編寫自己的RE時,這是一個非常有用的技巧,並且您有一組測試。


那麼...我們需要什麼樣的RE?那麼,我們需要一個積極的def和一個負面的hij,負面的hij需要在字符串的每個地方應用。你必須這樣想,因爲Tcl的負向前瞻約束總是使用非貪婪規則進行匹配。

讓我們切入追逐。您正在尋找的RE是^(?!.*hij.*$).*def

% check {^(?!.*hij.*$).*def} 
ab def hhh => 1 
abdefxxhijzz => 0 
hij => 0 
123defhijxyz => 0 
0def123hijxyz => 0 

這工作,因爲我們開始匹配的字符串的開始,我們首先要求(Tcl的RE比較默認情況下未錨定)。然後我們提出一個負面的前瞻,說我們不能匹配hij「here」(開始)和字符串結尾之間的某處;沒有錨定,這也可能成功通過不匹配在其他地方(自動機理論匹配器是這樣tricksy)。最後一部分是一個簡單的積極的「發現def」。

看看爲什麼錨定問題,看看這個非常相似的。

% check {(?!^.*hij.*$).*def} 
ab def hhh => 1 
abdefxxhijzz => 1 
hij => 0 
123defhijxyz => 1 
0def123hijxyz => 1 

爲什麼會失敗?那麼,考慮在第一個字母之後嘗試開始匹配;否定前瞻總是成功,因爲該錨失敗。

你也一定要小心你的測試用例:

% check {def(?!.*hij)} 
ab def hhh => 1 
abdefxxhijzz => 0 
hij => 0 
123defhijxyz => 0 
0def123hijxyz => 0 

這看起來不錯,短,但失敗abhijcdefxx; hij先於def,因此不會導致問題。


一般來說,如果你處理過濾行的集合,其實我建議使用:

# Read lines into list in $lines variable 

# Positive filter 
set linesWithDef [lsearch -all -inline -regexp $lines {def}] 

# Negative filter 
set linesWithoutHij [lsearch -all -inline -not -regexp $linesWithDef {hij}] 

這是精神上更類似於殼結構與管道grep小號...