regex - شرح - التعبير العادي لمطابقة خط لا يحتوي على كلمة؟




regular expression شرح (18)

أعلم أنه من الممكن مطابقة كلمة ثم عكس المباريات باستخدام أدوات أخرى (مثل grep -v ). ومع ذلك ، أود أن أعرف ما إذا كان من الممكن مطابقة الخطوط التي لا تحتوي على كلمة معينة (مثل hede) باستخدام تعبير عادي.

إدخال:

hoho
hihi
haha
hede

الشفرة:

grep "<Regex for 'doesn't contain hede'>" input

النتيجة المرجوة:

hoho
hihi
haha

المعايير

قررت تقييم بعض الخيارات المعروضة ومقارنة أدائها ، وكذلك استخدام بعض الميزات الجديدة. قياس الأداء على محرك .NET Regex: http://regexhero.net/tester/

نص المؤشر المعياري:

يجب ألا تتطابق السطور السبعة الأولى ، لأنها تحتوي على التعبير الذي تم البحث عنه ، في حين يجب أن تتطابق الخطوط السبعة المنخفضة!

Regex Hero is a real-time online Silverlight Regular Expression Tester.
XRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex HeroRegex HeroRegex HeroRegex HeroRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her Regex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.Regex Hero
egex Hero egex Hero egex Hero egex Hero egex Hero egex Hero Regex Hero is a real-time online Silverlight Regular Expression Tester.
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRegex Hero is a real-time online Silverlight Regular Expression Tester.

Regex Her
egex Hero
egex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her is a real-time online Silverlight Regular Expression Tester.
Nobody is a real-time online Silverlight Regular Expression Tester.
Regex Her o egex Hero Regex  Hero Reg ex Hero is a real-time online Silverlight Regular Expression Tester.

النتائج:

النتائج هي Iterations في الثانية كمتوسط ​​3 أشواط - عدد أكبر = أفضل

01: ^((?!Regex Hero).)*$                    3.914   // Accepted Answer
02: ^(?:(?!Regex Hero).)*$                  5.034   // With Non-Capturing group
03: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
04: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
05: ^(?(?=.*?Regex Hero)(?#fail)|.*)$       7.371   // Logic Branch: Find Regex Hero? match nothing, else anything

P1: ^(?(?=.*?Regex Hero)(*FAIL)|(*ACCEPT))  ?????   // Logic Branch in Perl - Quick FAIL
P2: .*?Regex Hero(*COMMIT)(*FAIL)|(*ACCEPT) ?????   // Direct COMMIT & FAIL in Perl

نظرًا لأن .NET لا يدعم إجراءات العمل (* FAIL ، إلخ.) لم أتمكن من اختبار الحلول P1 و P2.

ملخص:

حاولت اختبار معظم الحلول المقترحة ، بعض التحسينات ممكنة لبعض الكلمات. على سبيل المثال ، إذا لم يكن الحرفان الأولان من سلسلة البحث هو نفسه ، فيمكن توسيع الإجابة 03 إلى ^(?>[^R]+|R+(?!egex Hero))*$ مما يؤدي إلى كسب أداء صغير.

ولكن يبدو أن أسرع حل يمكن قراءته وأقصى قدر من الأداء هو 05 باستخدام عبارة مشروطة أو 04 باستخدام مقياس الكميات. أعتقد أن حلول بيرل يجب أن تكون أسرع وأكثر سهولة في القراءة.


كيفية استخدام الأشرطة التحكم التراجع PCRE لتتناسب مع خط لا يحتوي على كلمة واحدة

هذه طريقة لم أشاهدها من قبل:

/.*hede(*COMMIT)^|/

كيف تعمل

أولاً ، يحاول العثور على "hede" في مكان ما في السطر. إذا نجح ، في هذه المرحلة ، (*COMMIT)يخبر المحرك ، ليس فقط لا التراجع في حالة الفشل ، ولكن أيضا عدم محاولة أي مزيد من المطابقة في هذه الحالة. بعد ذلك ، نحاول مطابقة شيء لا يمكن أن يتطابق (في هذه الحالة ، ^).

إذا كان السطر لا يحتوي على "hede" ، فإن البديل الثاني ، subpattern فارغ ، يتطابق بنجاح مع سلسلة الموضوع.

هذه الطريقة ليست أكثر فعالية من lookahead سلبي ، لكنني أحسب أنني سأضعها هنا فقط في حال وجدها شخصًا رائعًا وتجد استخدامًا لها لتطبيقات أخرى أكثر تشويقًا.


FWIW ، حيث أن اللغات العادية (الملقب بلغات عقلانية) مغلقة تحت التكامل ، فمن الممكن دائمًا العثور على تعبير عادي (يُعرف أيضًا باسم التعبير العقلاني) ينفي تعبيرًا آخر. لكن لا توجد أدوات كثيرة تنفذ هذا.

يدعم Vcsn هذا المشغل (الذي يشير إلى {c} ، postfix).

عليك أولاً تحديد نوع lal_char : التسميات عبارة عن حرف ( lal_char ) للاختيار من lal_char إلى z على سبيل المثال (تعريف الأبجدية عند العمل مع المكمل ، بالطبع ، مهم جدًا) ، و "القيمة" المحسوبة لكل كلمة مجرد منطقية: true أن الكلمة مقبولة ، false ، مرفوضة.

في بايثون:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z} → 𝔹

ثم أدخل تعبيرك:

In [6]: e = c.expression('(hede){c}'); e
Out[6]: (hede)^c

تحويل هذا التعبير إلى إنسان:

In [7]: a = e.automaton(); a

أخيرا ، تحويل هذا automaton إلى تعبير بسيط.

In [8]: print(a.expression())
        \e+h(\e+e(\e+d))+([^h]+h([^e]+e([^d]+d([^e]+e[^]))))[^]*

حيث يتم عادة الرمز + | ، يشير \e إلى الكلمة الفارغة ، و [^] عادة ما يكتب . (أي شخصية). لذا ، مع قليل من إعادة الكتابة ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).* .

يمكنك الاطلاع على هذا المثال ، وتجربة Vcsn عبر الإنترنت there .


إذا كنت ترغب في مطابقة حرف لإبطال كلمة مشابهة لفئة الأحرف السلبية:

على سبيل المثال ، سلسلة:

<?
$str="aaa        bbb4      aaa     bbb7";
?>

لا تستخدم:

<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>

استعمال:

<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>

لاحظ "(?!bbb)." ليس lookbehind ولا lookahead ، فإنه lookcurrent ، على سبيل المثال:

"(?=abc)abcde", "(?!abc)abcde"

إليك شرحًا جيدًا لسبب عدم سهولة نفي تعبير منطقي تعسفي. لا بد لي من الاتفاق مع الإجابات الأخرى ، على الرغم من ذلك: إذا كان هذا هو أي شيء غير مسألة افتراضية ، فإن التعبير المعتاد ليس هو الخيار الصحيح هنا.


إليك كيف سأقوم بذلك:

^[^h]*(h(?!ede)[^h]*)*$

دقيقة وفعالة أكثر من الإجابات الأخرى. وهي تطبق أسلوب كفاءة "فرونت-ذا-لوب" لفريدل وتتطلب تراجعًا أقل بكثير.


الإجابات المقدمة جيدة تمامًا ، فقط نقطة أكاديمية:

التعبيرات العادية في معنى علوم الكمبيوتر النظرية ليست قادرة على القيام بذلك من هذا القبيل. بالنسبة لهم كان عليهم أن ينظروا إلى شيء كهذا:

^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$) 

هذا لا يفعل سوى مباراة كاملة. سيكون القيام به للمباريات الفرعية أكثر صعوبة.


بما أنه لم يعط أي شخص آخر إجابة مباشرة على السؤال الذي طُرح ، سأفعل ذلك.

الجواب هو أنه مع POSIX grep ، من المستحيل إرضاء هذا الطلب حرفيًا:

grep "Regex for doesn't contain hede" Input

والسبب هو أن POSIX grep مطلوب فقط للعمل مع التعبيرات العادية العادية ، التي هي ببساطة ليست قوية بما يكفي لإنجاز تلك المهمة (فهي غير قادرة على تحليل اللغات العادية ، بسبب عدم التناوب والتجميع).

ومع ذلك ، جنو grep تنفذ ملحقات تسمح بذلك. على وجه الخصوص ، \| هو مشغل التشغيل البديل في تنفيذ GNU لـ BREs ، و \( و \) هي عوامل التشغيل المجمعة. إذا كان محرك التعبير العادي الخاص بك يدعم التنسيقات ، وتعبيرات القوس السلبية ، والتجمع ونجمة Kleene ، وقادر على الارتساء إلى بداية ونهاية السلسلة ، فهذا كل ما تحتاج إليه لهذا النهج.

مع gnu grep ، سيكون شيء من هذا القبيل:

grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" Input

(تم العثور عليه مع Grail وبعض التحسينات الإضافية التي تم إجراؤها يدويًا).

يمكنك أيضًا استخدام أداة تقوم بتنفيذ التعبيرات العادية الموسعة ، مثل egrep ، للتخلص من الخطوط المائلة العكسية:

egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" Input

هنا برنامج نصي لاختباره (لاحظ أنه ينشئ ملف testinput.txt في الدليل الحالي):

#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"

# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede

h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)

في نظامي ، يتم طباعة:

Files /dev/fd/63 and /dev/fd/62 are identical

كما هو متوقع.

بالنسبة لأولئك المهتمين بالتفاصيل ، فإن التقنية المستخدمة هي تحويل التعبير العادي الذي يطابق الكلمة إلى إنسان آلي محدود ، ثم عكس آلي عن طريق تغيير كل حالة قبول إلى عدم القبول والعكس ، ثم تحويل حرف FA الناتج إلى تعبير عادي.

وأخيرًا ، كما لاحظ الجميع ، إذا كان محرك التعبير العادي يدعم خاصية lookahead السلبية ، فهذا يبسط المهمة كثيرًا. على سبيل المثال ، مع GNU grep:

grep -P '^((?!hede).)*$' Input

تحديث: لقد عثرت مؤخرًا على مكتبة FormalTheory لـ Kendall Hopkins ، والتي تمت كتابتها في PHP ، والتي توفر وظيفة مشابهة لـ Grail. باستخدامه ، ومُبسِّط كتبته بنفسي ، تمكنت من كتابة مولد عبر الإنترنت للتعبيرات العادية السلبية ، مع وضع عبارة مدخلات (فقط الأحرف الأبجدية الرقمية وحروف الفضاء المدعومة حاليًا): http://www.formauri.es/personal/pgimeno/misc/non-match-regex/

ل hedeذلك النواتج:

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

وهو ما يعادل ما سبق.


لم يحدد OP أو وضع علامة على المشاركة للإشارة إلى السياق (لغة البرمجة ، المحرر ، الأداة) سيتم استخدام Regex ضمن.

بالنسبة لي ، أحتاج في بعض الأحيان للقيام بذلك أثناء تحرير ملف باستخدام Textpad .

يدعم Textpad بعض Regex ، لكنه لا يدعم lookahead أو lookbehind ، لذلك يستغرق بضع خطوات.

إذا كنت أتطلع إلى الاحتفاظ بجميع الأسطر التي لا تحتوي على سلسلة hede ، hede على النحو التالي:

1. بحث / استبدال الملف بأكمله لإضافة "علامة" فريدة من نوعها إلى بداية كل سطر يحتوي على أي نص.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. حذف كافة الأسطر التي تحتوي على سلسلة hede (سلسلة الاستبدال فارغة):

    Search string:<@#-unique-#@>.*hede.*\n  
    Replace string:<nothing>  
    Replace-all  

3. في هذه المرحلة ، لا تحتوي جميع الأسطر المتبقية على سلسلة hede . أزل "العلامة" الفريدة من جميع الأسطر (سلسلة الاستبدال فارغة):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  

الآن لديك النص الأصلي مع جميع الخطوط التي تحتوي على إزالة سلسلة hede .

إذا كنت أتطلع إلى القيام بشيء ما إلى الخطوط التي لا تحتوي على سلسلة hede ، hede ذلك كما يلي:

1. بحث / استبدال الملف بأكمله لإضافة "علامة" فريدة من نوعها إلى بداية كل سطر يحتوي على أي نص.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. بالنسبة إلى جميع الأسطر التي تحتوي على سلسلة hede ، أزل "العلامة" الفريدة:

    Search string:<@#-unique-#@>(.*hede)
    Replace string:\1  
    Replace-all  

3. عند هذه النقطة ، لا تحتوي جميع الأسطر التي تبدأ بـ "العلامة" الفريدة ، على سلسلة hede . أستطيع الآن أن أفعل شيئًا آخر إلى هذه الخطوط فقط.

4. عند انتهائي ، أقوم بإزالة "العلامة" الفريدة من جميع الأسطر (سلسلة الاستبدال فارغة):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  

ليس regex ، لكنني وجدته منطقي ومفيد لاستخدام greps التسلسلي مع الأنابيب للقضاء على الضوضاء.

على سبيل المثال. البحث في ملف التكوين اباتشي دون جميع التعليقات ،

grep -v '\#' /opt/lampp/etc/httpd.conf      # this gives all the non-comment lines

و

grep -v '\#' /opt/lampp/etc/httpd.conf |  grep -i dir

منطق تسلسل grep's (ليس تعليق) و (يطابق dir)


مع هذا ، يمكنك تجنب اختبار lookahead على كل المواقف:

/^(?:[^h]+|h++(?!ede))*+$/

ما يعادل (ل. net):

^(?>(?:[^h]+|h+(?!ede))*)$

الجواب القديم:

/^(?>[^h]+|h+(?!ede))*$/

منذ إدخال ruby-2.4.1 ، يمكننا استخدام المشغل الغائب الجديد في تعبيرات Ruby العادية

من doc الرسمي

(?~abc) matches: "", "ab", "aab", "cccc", etc.
It doesn't match: "abc", "aabc", "ccccabc", etc.

وهكذا ، في حالتك ^(?~hede)$ يقوم بهذه المهمة نيابة عنك

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]

إجابة:

^((?!hede).)*$

تفسير:

^ بداية السلسلة ، ( المجموعة والتقاطها إلى \ 1 (0 أو أكثر من مرة (تطابق أكبر مبلغ ممكن)) ،
(?! انظر إلى الأمام لمعرفة ما إذا لم يكن هناك ،

hede سلسلة الخاص بك ،

نهاية نهاية النظر . أي حرف باستثناء \ n ،
)* نهاية \ 1 (ملاحظة: نظرًا لأنك تستخدم مقياسًا على هذا الالتقاط ، سيتم فقط تخزين التكرار الأخير للنمط الذي تم التقاطه في \ 1)
$ قبل \ n ، ونهاية السلسلة


من خلال فعل PCRE (*SKIP)(*F)

^hede$(*SKIP)(*F)|^.*$

هذا من شأنه أن يتخطى تماما الخط الذي يحتوي على سلسلة hede بالضبط hede جميع الخطوط المتبقية.

DEMO

تنفيذ الأجزاء:

دعنا ننظر في التعبير المعتاد أعلاه عن طريق تقسيمه إلى قسمين.

  1. جزء قبل رمز. جزء لا ينبغي أن يقابل .

    ^hede$(*SKIP)(*F)
    
  2. جزء بعد رمز. جزء يجب أن تكون متطابقة .

    ^.*$
    

الجزء الأول

سيبدأ محرك regex تنفيذه من الجزء الأول.

^hede$(*SKIP)(*F)

تفسير:

  • ^ يؤكد أننا في البداية.
  • hede مع سلسلة hede
  • $ يؤكد أننا في نهاية السطر.

لذلك فإن الخط الذي يحتوي على سلسلة hede سيتم مطابقته. بمجرد رؤية محرك regex التالي (*SKIP)(*F) ( ملاحظة: يمكنك كتابة (*F) ) كفعل (*FAIL) ، فإنه يتخطى وجعل المباراة بالفشل. | يُعرف بالتغيير أو العامل المنطقي OR المُضاف إلى جانب فعل PCRE الذي يتطابق مع جميع الحدود الموجودة بين كل حرف على كل الأسطر باستثناء السطر الذي يحتوي على سلسلة hede . شاهد العرض here . أي ، يحاول مطابقة الأحرف من السلسلة المتبقية. الآن سيتم تنفيذ التعبير المعتاد في الجزء الثاني.

الجزء 2

^.*$

تفسير:

  • ^ يؤكد أننا في البداية. أي أنه يطابق كل سطر يبدأ باستثناء السطر الموجود في خط hede . شاهد العرض here .
  • .* في وضع Multiline ، . يتطابق مع أي حرف باستثناء أحرف السطر الأول أو أحرف الإرجاع. و * سيكرر الحرف السابق صفراً أو أكثر. هكذا .* سوف تتطابق مع الخط بأكمله. شاهد العرض here .

    لماذا قمت بإضافة. * بدلاً من. +؟

    لأن .* قد يتطابق مع سطر فارغ ولكن .+ لن يتطابق مع سطر فارغ. نريد أن نطابق جميع السطور ما عدا hede ، قد يكون هناك احتمال لخطوط فارغة أيضًا في الإدخال. لذلك يجب عليك استخدام .* بدلاً من .+ . .+ سيكرر الحرف السابق مرة واحدة أو أكثر. انظر .* يتطابق مع سطر فارغ here .

  • $ نهاية مرساة الخط ليست ضرورية هنا.


لا أفهم الحاجة إلى تعابير معقدة أو حتى lookaheads هنا:

/hede|^(.*)$/gm

لا تضع في مجموعة أسر الأشياء التي لا تريدها ، بل استخدم واحدة لكل شيء آخر. سيطابق هذا جميع الأسطر التي لا تحتوي على "hede".


الحل الأبسط هو استخدام المشغل وليس !

سيحتاج بيان if الخاص بك إلى مطابقة "يحتوي على" ولا يطابق "يستثني".

var contains = /abc/;
var excludes =/hede/;

if(string.match(contains) && !(string.match(excludes))){  //proceed...

أعتقد أن مصممي RegEx توقعوا عدم استخدام المشغلين.


ربما ستجد ذلك على Google أثناء محاولة كتابة تعبير عادي قادر على مطابقة أجزاء السطر (على عكس الخطوط الكاملة) التي لا تحتوي على سلسلة فرعية. Tooke لي بعض الوقت لمعرفة ، لذلك سأشارك:

بالنظر إلى سلسلة: <span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

أرغب في مطابقة <span>العلامات التي لا تحتوي على السلسلة الفرعية "سيئة".

/<span(?:(?!bad).)*?>سوف تتطابق <span class=\"good\">و <span class=\"ugly\">.

لاحظ أن هناك مجموعتين (الطبقات) من الأقواس:

  • الأعمق هو ل lookahead السلبي (ليست مجموعة الالتقاط)
  • لقد فُسِّر الأبعد من قِبل روبي كمجموعة التقاط لكننا لا نريده أن يكون مجموعة التقاط ، لذا أضفت: في بدايته ولم يعد يُفسر على أنه مجموعة التقاط.

عرض في روبي:

s = '<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>'
s.scan(/<span(?:(?!bad).)*?>/)
# => ["<span class=\"good\">", "<span class=\"ugly\">"]

في اللغة TXR تدعم التعابير المنطقية النفي.

$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)'  Input

مثال أكثر تعقيدًا: تطابق جميع الأسطر التي تبدأ aوتنتهي باستخدام z، لكن لا تحتوي على السلسلة الفرعية hede:

$ txr -c '@(repeat)
@{nothede /a.*z&~.*hede.*/}
@(do (put-line nothede))
@(end)' -
az         <- echoed
az
abcz       <- echoed
abcz
abhederz   <- not echoed; contains hede
ahedez     <- not echoed; contains hede
ace        <- not echoed; does not end in z
ahedz      <- echoed
ahedz

لا يعد نفي Regex مفيدًا بشكل خاص من تلقاء نفسه ، ولكن عندما يكون لديك أيضًا تقاطع ، تصبح الأشياء مثيرة للاهتمام ، حيث أن لديك مجموعة كاملة من العمليات المعقدة: يمكنك التعبير عن "المجموعة التي تطابق هذا ، باستثناء الأشياء التي تطابق ذلك".





regex-group