일치 할 정규식 패턴, 다음 경우 제외… / 다음 사이 제외


108

-편집 -현재 답변에는 유용한 아이디어가 있지만 100 % 이해하고 재사용 할 수있는 더 완전한 것을 원합니다. 그래서 현상금을 설정했습니다. 또한 어디에서나 작동하는 아이디어는 표준 구문이 아닌 것보다 나에게 더 좋습니다.\K

이 질문은 일부 상황 s1 s2 s3을 제외하고 패턴을 일치시킬 수있는 방법에 관한 것입니다. 내 의미를 보여주기 위해 구체적인 예를 들었지만 100 % 이해할 수있는 일반적인 답변을 선호하므로 다른 상황에서 재사용 할 수 있습니다.

\b\d{5}\b세 가지 상황이 아닌 s1 s2 s3을 사용하여 다섯 자리를 일치시키고 싶습니다 .

s1 : 이 문장처럼 마침표로 끝나는 줄에 있지 않습니다.

s2 : 괄호 안에는 어디에도 없습니다.

s3 : 다음으로 시작 if(하고 끝나는 블록 내부가 아닙니다 .//endif

나는 s1 s2 s3 중 하나를 예견과 예견으로, 특히 C # lookbehind 또는 \KPHP에서 해결하는 방법을 알고 있습니다.

예를 들어

s1 (?m)(?!\d+.*?\.$)\d+

C # lookbehind를 사용하는 s3 (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

PHP \ K가있는 s3 (?:(?:if\(.*?//endif)\D*)*\K\d+

하지만 여러 조건이 섞여서 머리가 터집니다. 더 나쁜 소식은 다른 조건 s4 s5를 다른 시간에 추가해야 할 수도 있다는 것입니다.

좋은 소식은 PHP, C #, Python 또는 이웃 세탁기와 같은 가장 일반적인 언어를 사용하여 파일을 처리해도 상관 없다는 것입니다. :) 저는 Python 및 Java의 초보자이지만 솔루션이 있는지 배우고 싶습니다.

그래서 나는 누군가가 유연한 레시피를 생각하는지 알아보기 위해 여기에 왔습니다.

힌트는 괜찮습니다. 전체 코드를 제공 할 필요가 없습니다. :)

감사합니다.


1
\K특별한 PHP 구문이 아닙니다. 당신이 말하고 싶은 것을 정교하고 명확하게하십시오. "복잡한"솔루션이 필요하지 않다고 말하는 것을 목표로한다면 무엇이 복잡한 지, 왜 그런지 말해야합니다.
hakre 2014 년

@hakre 루비가 지금 그것을 사용하고 펄에서 시작했기 때문에 의미합니까?
Hans Schindler 2014 년

1
아니요, PHP (루비도 아님)가 아닌 PCRE이기 때문입니다. Perl은 다르지만 PCRE Perl Regex와 호환되는 것을 목표 로합니다.
hakre 2014 년

s2 및 s3 요구 사항이 모순되는 것 같습니다. (S2) 괄호는 항상 일치와 중첩 될 수 있음을 의미하지만, S3해야합니다 : "if("열린 괄호가 아닌 함께, 폐쇄 ")", 오히려와 : "//endif"? 그리고 s3의 경우 if 절을 다음으로 닫아야 함을 의미했다면 "//endif)"s3 요구 사항은 s2의 하위 집합입니다.
ridgerunner

@hakre 예 저는 PCRE를 알고 있지만 설명하기 위해 질문은 프로그래밍 언어에 관한 것입니다 ...라고 말합니다 especially in C# lookbehind or \K in PHP...하지만 C #은 C #뿐만 아니라 .NET이므로 불평 할 수 있으므로 .NET이 아닌 C #이라고 말합니다. 오니 구라 마가 아니라 루비도 나쁘다 ... PCRE를 사용하는 다른 언어가 있습니까? Notepad ++ 또는 서버 도구에 대해 이야기하지 않음 이것은 언어에서 기능을 사용하는 것에 대한 질문입니다. 설명을 바라며 잘못
Hans Schindler

답변:


205

한스, 나는 미끼를 가져다가 내 이전 대답을 구체화하겠습니다. "좀 더 완전한 것"을 원한다고 하셨기 때문에 긴 대답이 마음에 들지 않으시 길 바랍니다. 몇 가지 배경 지식부터 시작하겠습니다.

우선, 이것은 훌륭한 질문입니다. 특정 컨텍스트 (예 : 코드 블록 또는 괄호 내부)를 제외하고 특정 패턴 일치에 대한 질문이 자주 있습니다. 이러한 질문은 종종 상당히 어색한 해결책을 제시합니다. 따라서 여러 상황 에 대한 귀하의 질문은 특별한 도전입니다.

놀라다

놀랍게도 일반적이고 구현하기 쉬우 며 유지 관리하기 좋은 효율적인 솔루션이 하나 이상 있습니다. 그것은 모든 정규식 맛을 작동 코드에서 캡처 그룹을 검사 할 수 있습니다. 그리고 처음에는 당신과 다르게 들릴 수있는 몇 가지 일반적인 질문에 대답합니다. "도너츠를 제외한 모든 항목 일치", "모두 교체 ...", "엄마의 블랙리스트에있는 단어를 제외한 모든 단어 일치", "무시" 태그 ","기울임 꼴이 아닌 경우 온도 일치 "...

슬프게도이 기술은 잘 알려져 있지 않습니다. 나는 그것을 사용할 수있는 20 개의 SO 질문 중 하나만이 그것을 언급하는 하나의 답을 가지고 있다고 추정합니다. 이는 아마도 50 개 또는 60 개의 답 중 하나를 의미합니다. 코멘트에서 Kobi와의 교환을 참조하십시오. 이 기술은 (낙관적으로) "역대 최고의 정규식 트릭"이라고 부르는 이 기사 에서 자세히 설명 합니다. 자세히 설명하지 않고 기술이 어떻게 작동하는지 확실히 이해하려고 노력할 것입니다. 다양한 언어로 된 자세한 내용과 코드 샘플을 보려면 해당 리소스를 참조하십시오.

더 잘 알려진 변형

동일한 작업을 수행하는 Perl 및 PHP에 특정한 구문을 사용하는 변형이 있습니다. CasimiretHippolyteHamZa 와 같은 정규식 마스터의 손에서 SO에서 볼 수 있습니다. 아래에서 이에 대해 자세히 설명하지만 여기서는 모든 정규식 버전에서 작동하는 일반 솔루션에 중점을 둡니다 (코드에서 캡처 그룹을 검사 할 수있는 한).

모든 배경에 감사드립니다, zx81 ...하지만 레시피는 무엇입니까?

주요 사실

이 메서드는 그룹 1 캡처에서 일치를 반환합니다. 전체 경기에 대해서는 전혀 신경 쓰지 않습니다.

사실, 트릭은 우리가 원하지 않는 다양한 컨텍스트 ( |OR / 교대를 사용하여 이러한 컨텍스트를 연결 )를 일치시켜 "중화"하는 것입니다. 원치 않는 모든 컨텍스트를 일치시킨 후 교체의 마지막 부분은 우리 원하는 것과 일치 하여 그룹 1에 캡처합니다.

일반적인 레시피는

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

이것은 일치 할 Not_this_context것이지만, 어떤 의미에서 일치는 쓰레기통에 들어갑니다. 왜냐하면 우리는 전체 경기를 보지 않을 것이기 때문입니다. 우리는 그룹 1 캡처 만 봅니다.

귀하의 경우, 귀하의 숫자와 세 가지 컨텍스트를 무시하면 다음을 수행 할 수 있습니다.

s1|s2|s3|(\b\d+\b)

s1, s2 및 s3를 둘러보기로 피하는 대신 실제로 일치시키기 때문에 s1, s2 및 s3에 대한 개별 표현식은 하루 동안 명확하게 유지 될 수 있습니다. (그들은 a의 각 측면에있는 하위 표현식입니다 |)

전체 표현식은 다음과 같이 작성할 수 있습니다.

(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)

데모를 참조하십시오 (그러나 오른쪽 아래 창의 캡처 그룹에 중점을 둡니다.)

정신적으로 각 |구분 기호 에서이 정규식을 분할하려고하면 실제로는 일련의 매우 간단한 식입니다.

여유 공간을 지원하는 플레이버의 경우 특히 잘 읽습니다.

(?mx)
      ### s1: Match line that ends with a period ###
^.*\.$  
|     ### OR s2: Match anything between parentheses ###
\([^\)]*\)  
|     ### OR s3: Match any if(...//endif block ###
if\(.*?//endif  
|     ### OR capture digits to Group 1 ###
(\b\d+\b)

이것은 매우 읽고 유지하기 쉽습니다.

정규식 확장

더 많은 상황 s4 및 s5를 무시하려면 왼쪽에 더 많은 상황을 추가합니다.

s4|s5|s1|s2|s3|(\b\d+\b)

어떻게 작동합니까?

원하지 않는 컨텍스트는 왼쪽의 대체 목록에 추가됩니다. 일치하지만 이러한 전체 일치는 검사되지 않으므로 일치하는 것은 "쓰레기통"에 넣는 방법입니다.

그러나 원하는 콘텐츠는 그룹 1로 캡처됩니다. 그런 다음 프로그래밍 방식으로 그룹 1이 설정되어 있고 비어 있지 않은지 확인해야합니다. 이것은 사소한 프로그래밍 작업입니다 (그리고 나중에 어떻게 수행되는지에 대해 설명하겠습니다). 특히 한 눈에 이해하고 필요에 따라 수정하거나 확장 할 수있는 간단한 정규식을 남긴다는 점을 고려하면 더욱 그렇습니다.

나는 항상 시각화의 팬은 아니지만이 방법은 방법이 얼마나 간단한 지 잘 보여줍니다. 각 "라인"은 잠재적 인 일치 항목에 해당하지만 하단 라인 만 그룹 1로 캡처됩니다.

정규식 시각화

Debuggex 데모

Perl / PCRE 변형

위의 일반적인 솔루션과 달리, 적어도 @CasimiretHippolyte 및 @HamZa와 같은 정규식 신의 손에서 SO에서 자주 볼 수있는 Perl 및 PCRE의 변형이 있습니다. 그것은:

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

귀하의 경우 :

(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b

이 변형은 컨텍스트 s1, s2 및 s3에서 일치하는 콘텐츠를 건너 뛰기 때문에 사용하기가 조금 더 쉽습니다. 따라서 그룹 1 캡처를 검사 할 필요가 없습니다 (괄호가 사라짐에 유의). 일치 항목에는whatYouWant

그 주 (*F), (*FAIL)그리고 (?!)모두 같은 것입니다. 더 모호하게하고 싶다면 다음을 사용할 수 있습니다.(*SKIP)(?!)

이 버전의 데모

응용

다음은이 기술이 쉽게 해결할 수있는 몇 가지 일반적인 문제입니다. 단어 선택은 이러한 문제 중 일부가 실제로는 동일하지만 다르게 들리도록 만들 수 있습니다.

  1. 같은 태그의 아무 곳이나 제외하고 foo를 어떻게 일치시킬 수 <a stuff...>...</a>있습니까?
  2. <i>태그 또는 자바 스크립트 스 니펫 (추가 조건)을 제외하고 foo를 어떻게 일치시킬 수 있습니까?
  3. 이 블랙리스트에없는 모든 단어를 어떻게 일치시킬 수 있습니까?
  4. SUB ... END SUB 블록 안의 내용을 어떻게 무시할 수 있습니까?
  5. s1 s2 s3를 제외한 모든 것을 어떻게 일치시킬 수 있습니까?

그룹 1 캡처를 프로그래밍하는 방법

당신은 코드가 아니라, 완성을 위해 ... 그룹 1을 검사하는 코드는 분명히 당신이 선택한 언어에 달려 있습니다. 어쨌든 일치를 검사하는 데 사용할 코드에 두 줄 이상을 추가해서는 안됩니다.

확실하지 않다면 앞서 언급 한 기사 의 코드 샘플 섹션 을 살펴볼 것을 권장합니다.이 섹션에서는 상당수의 언어에 대한 코드를 제공합니다.

대안

질문의 복잡성과 사용 된 정규식 엔진에 따라 몇 가지 대안이 있습니다. 다음은 여러 조건을 포함하여 대부분의 상황에 적용 할 수있는 두 가지입니다. 제 생각에는 s1|s2|s3|(whatYouWant)명확성이 항상 승리하기 때문에 레시피 만큼 매력적이지 않습니다 .

1. 교체 후 일치.

이상하게 들리지만 많은 환경에서 잘 작동하는 좋은 솔루션은 두 단계로 작업하는 것입니다. 첫 번째 정규식은 잠재적으로 충돌 할 수있는 문자열을 대체하여 무시하려는 컨텍스트를 무력화합니다. 일치 만하려는 경우 빈 문자열로 바꾼 다음 두 번째 단계에서 일치를 실행할 수 있습니다. 교체하려면 먼저 무시할 문자열을 고유 한 것으로 교체 할 수 있습니다. 예를 들어 숫자를 고정 너비 체인 @@@. 이 교체 후에는 원하는 것을 자유롭게 교체 할 수 있으며 고유 한 @@@문자열 을 되돌려 야합니다 .

2. 둘러보기.

원본 게시물은 둘러보기를 사용하여 단일 조건을 제외하는 방법을 이해하고 있음을 보여주었습니다. 당신은 C #이 이것을 위해 훌륭하다고 말했고 당신이 옳다고 말했지만 이것이 유일한 옵션은 아닙니다. 예를 들어 C #, VB.NET 및 Visual C ++에서 찾을 수있는 .NET 정규식 버전과 Python에서 regex대체 할 아직 실험적인 모듈 re은 무한 너비 룩백을 지원하는 유일한 두 엔진입니다. 이러한 도구를 사용하면 하나의 룩 비하인드에서 하나의 조건으로 뒤를 볼 수있을뿐만 아니라 경기와 경기를 벗어난 모습을 처리 할 수 ​​있으므로 룩어 헤드와 조정할 필요가 없습니다. 더 많은 조건? 더 많은 둘러보기.

C #에서 s3에 대한 정규식을 재활용하면 전체 패턴이 다음과 같이 보일 것입니다.

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

하지만 지금 쯤이면 내가 이것을 추천하지 않는다는 것을 알죠?

삭제

@HamZa와 @Jerry는 삭제하려는 경우 추가 트릭을 언급 할 것을 제안했습니다 WhatYouWant. 일치시킬 레시피 WhatYouWant(그룹 1로 캡처)가 였음 을 기억 s1|s2|s3|(WhatYouWant)하십니까? 의 모든 인스턴스를 삭제하려면 WhatYouWant정규식을 다음과 같이 변경합니다.

(s1|s2|s3)|WhatYouWant

대체 문자열의 경우 $1. 여기서 일어나는 s1|s2|s3일은 일치하는 각 인스턴스에 대해 $1교체가 해당 인스턴스를 자체로 대체한다는 것입니다 (에서 참조 $1). 반면에 WhatYouWant일치하는 경우 빈 그룹으로 교체되고 다른 항목은 삭제되므로 삭제됩니다. 이 데모를 참조하십시오 .이 멋진 추가 기능을 제안 해 주신 @HamZa 및 @Jerry에게 감사드립니다.

교체

이것은 우리에게 교체품을 가져 오며, 이에 대해서는 간단히 설명하겠습니다.

  1. 아무것도 바꾸지 않는 경우 위의 "삭제"트릭을 참조하십시오.
  2. 교체 할 때 Perl 또는 PCRE를 사용하는 경우 (*SKIP)(*F)위에서 언급 한 변형을 사용하여 원하는 것과 정확히 일치하고 곧바로 교체하십시오.
  3. 다른 플레이버에서는 교체 함수 호출 내에서 콜백 또는 람다를 사용하여 일치를 검사하고 그룹 1이 설정된 경우 교체합니다. 이에 대한 도움이 필요하면 이미 참조 된 기사에서 다양한 언어로 된 코드를 제공합니다.

즐기세요!

아니, 잠깐만 더 있어요!

아, 아니, 내 회고록을 20 권으로 저장해 내년 봄에 공개하겠습니다.


2
@Kobi 두 부분으로 된 답장. 예, 어제 밤에 글을 쓰고 바닥에 글을 썼습니다. 자고 나중에 정리했습니다. :) 네, 속임수는 간단하지만, 사람들이 배제 문제를 해결하기 위해 사용하는 일반적인 도구의 일부가 아니기 때문에 "기본"이라는 당신의 인식을 공유하지 않습니다. 내가 "예외"또는 "하지 않는"또는 "내부 아님"문제를 검색했을 때, 단 하나의 대답 (투표없이) 만 제안했고, 다른 사람은 그렇게하지 않았습니다. 그건 그렇고 대단한 당신의 대답을 보지 못했습니다. :)
zx81 2014 년

2
죄송합니다. Rex의 "Best trick"은 작동하지 않습니다 ( 안정적으로 ). 일치 Tarzan하고 싶지만 큰 따옴표 안에있는 경우에는 그렇지 않습니다. : /no|no|(yes)/: 트릭 정규식은 같은 것 /"[^"]*"|Tarzan/(탈출 문자를 무시를). 이것은 많은 경우에 작동하지만 다음 유효한 JavaScript 텍스트에 적용하면 완전히 실패합니다 var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Rex의 트릭은 가능한 모든 구조가 일치 할 때만 작동합니다. 즉, 100 % 정확도를 보장하기 위해 텍스트를 완전히 구문 분석해야합니다.
ridgerunner

1
내가 거칠게 들리면 미안합니다. 그것은 확실히 내 의도가 아닙니다. 내 요점은 (위의 원래 질문에 대한 두 번째 의견에서와 같이) 올바른 솔루션이 검색되는 대상 텍스트에 크게 의존한다는 것입니다. 내 예제에는 작은 따옴표로 묶인 문자열 안에 큰 따옴표가 하나있는 대상 텍스트로 JavaScript 소스 코드가 있습니다. 다음 var bug1 = /"[^"]*"|(Tarzan)/gi;과 같은 리터럴 RegExp 일 수도 있고 동일한 효과를 가졌습니다 (이 두 번째 예제는 확실히 엣지 케이스가 아닙니다). 이 기술이 안정적으로 작동하지 않는 경우 더 많은 예를 인용 할 수 있습니다.
ridgerunner 2014

1
@ridgerunner 나는 항상 당신의 의견을 듣는 것을 좋아합니다. 그것은 단지 나에게 부당하게 가혹하게 들립니다. 문자열에 "잘못된 경고"가 포함될 수 있음을 알게되면 모두 패턴을 조정합니다. 예를 들어, 문자열 매처를 떨어 뜨릴 수있는 이스케이프 된 따옴표를 포함 할 수있는 문자열을 일치 시키려면 (?<!\\)"(?:\\"|[^"\r\n])*+" 이유가없는 한 You do n't pull the big guns를 사용할 수 있습니다 . 솔루션의 원리는 여전히 유효합니다. 왼쪽에 놓을 패턴을 표현할 수 없다면, 그것은 다른 이야기입니다. 다른 해결책이 필요합니다. 그러나 솔루션은 광고하는대로 수행합니다.
ZX81

1
이 답변은 @funkwurm 사용자 가 Stack Overflow Regular Expressions FAQ 에 추가했습니다 .
aliteralmind

11

세 가지 다른 일치를 수행하고 프로그램 내 조건 논리를 사용하여 세 가지 상황의 조합을 처리합니다. 하나의 거대한 정규식으로 모든 것을 처리 할 필요는 없습니다.

편집 : 질문이 더 흥미로워 졌기 때문에 조금 확장하겠습니다 :-)

여기서 캡처하려는 일반적인 아이디어는 특정 정규식 패턴과 일치시키는 것이지만 테스트 문자열에 특정 다른 (숫자가 될 수 있음) 패턴이있는 경우에는 그렇지 않습니다. 다행히도 프로그래밍 언어를 활용할 수 있습니다. 정규식을 단순하게 유지하고 복합 조건을 사용하십시오. 이 아이디어를 재사용 가능한 구성 요소에 캡처하는 것이 가장 좋은 방법이므로이를 구현하는 클래스와 메서드를 만들어 보겠습니다.

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class MatcherWithExceptions {
  private string m_searchStr;
  private Regex m_searchRegex;
  private IEnumerable<Regex> m_exceptionRegexes;

  public string SearchString {
    get { return m_searchStr; }
    set {
      m_searchStr = value;
      m_searchRegex = new Regex(value);
    }
  }

  public string[] ExceptionStrings {
    set { m_exceptionRegexes = from es in value select new Regex(es); }
  }

  public bool IsMatch(string testStr) {
    return (
      m_searchRegex.IsMatch(testStr)
      && !m_exceptionRegexes.Any(er => er.IsMatch(testStr))
    );
  }
}

public class App {
  public static void Main() {
    var mwe = new MatcherWithExceptions();

    // Set up the matcher object.
    mwe.SearchString = @"\b\d{5}\b";
    mwe.ExceptionStrings = new string[] {
      @"\.$"
    , @"\(.*" + mwe.SearchString + @".*\)"
    , @"if\(.*" + mwe.SearchString + @".*//endif"
    };

    var testStrs = new string[] {
      "1." // False
    , "11111." // False
    , "(11111)" // False
    , "if(11111//endif" // False
    , "if(11111" // True
    , "11111" // True
    };

    // Perform the tests.
    foreach (var ts in testStrs) {
      System.Console.WriteLine(mwe.IsMatch(ts));
    }
  }
}

따라서 위에서 검색 문자열 (5 자리 숫자), 여러 예외 문자열 ( s1 , s2s3 )을 설정 한 다음 여러 테스트 문자열과 일치 시키려고합니다. 인쇄 된 결과는 각 테스트 문자열 옆의 주석에 표시되어야합니다.


2
연속으로 세 개의 정규식을 일치시키는 것을 의미합니까? 정규식 1은 상황 1을 제거하고 (아마도 잘못된 숫자 만 삭제), r2는 s2를 제거하고, r3은 s3을 제거하고 남은 숫자와 일치합니까? 흥미로운 아이디어입니다.
Hans Schindler 2014 년

하, 그게 내가 당신을 찬성 한 이유입니다. :) 오해하지 마세요.이 특별한 경우에 제 답변이 더 효율적이고 유지 보수 가능하다고 생각합니다. 어제 추가 한 자유 간격 버전을 보셨습니까? 이것은 한 번의 패스이며 매우 쉽게 읽고 유지 관리 할 수 ​​있습니다. 그러나 나는 당신의 일과 당신의 확장 된 대답을 좋아합니다. 다시 찬성 할 수 없어서 죄송합니다. :)
zx81 2014 년

2

괄호 안에 있지 않다는 귀하의 요구 사항은 모든 경우에 대해 만족시킬 수 없습니다. 즉, 어떻게 든 (왼쪽과 )오른쪽에서 a 를 찾을 수 있다고 해서 항상 괄호 안에 있다는 의미는 아닙니다. 예 :

(....) + 55555 + (.....)-하지 괄호 안에 아직 (하고 )왼쪽과 오른쪽에

이제 자신이 영리하다고 생각하고 이전에 (만나지 않은 경우에만 왼쪽을 찾고 )오른쪽에서 그 반대를 찾을 수 있습니다. 이 경우에는 작동하지 않습니다.

((.....) + 55555 + (.....))- 내부가 폐쇄 되더라도 괄호 )(왼쪽으로 오른쪽으로.

정규식은 열린 괄호 수와 닫은 괄호 수를 계산할 수 없기 때문에 정규식을 사용하여 괄호 안에 있는지 알아내는 것은 불가능합니다.

모든위한 그 문자열의 모든 (가능성이 중첩) 괄호가 닫혀 있는지 확인, 정규식을 사용하여이 쉽게 작업을 고려 (하면 찾을 필요 ). 해결이 불가능하다는 것을 알게 될 것이며 정규식으로 해결할 수 없다면 모든 경우에 대해 괄호 안에 단어가 있는지 알아낼 수 없습니다. 모든 선행 (에는 해당하는 ).


2
아무도 중첩 된 괄호에 대해 아무 말도하지 않았으며 귀하의 사례 # 1은 zx81의 답변으로 잘 처리됩니다.
Dan Bechard

좋은 생각을 주셔서 감사합니다 :)하지만 중첩 된 괄호는이 질문에 대해 걱정하지 않습니다. 그것은 나쁜 상황에 대한 아이디어에 관한 것입니다. s1 s2 s3
Hans Schindler

물론 불가능한 것은 아닙니다! 이것이 바로 현재 구문 분석중인 괄호 수준을 추적해야하는 이유입니다.
MrWonderful

OP와 같은 일종의 CFG를 구문 분석하는 경우 문제가없는 LALR 또는 유사한 구문 분석기를 생성하는 것이 좋습니다.
RokL

2

Hans 괜찮다면 이웃의 세탁기 인 perl을 사용했습니다. :)

수정 됨 : 의사 코드 아래 :

  loop through input
  if line contains 'if(' set skip=true
        if skip= true do nothing
        else
           if line match '\b\d{5}\b' set s0=true
           if line does not match s1 condition  set s1=true
           if line does not match s2 condition  set s2=true
           if s0,s1,s2 are true print line 
  if line contains '//endif' set skip=false

input.txt 파일이 주어지면 :

tiago@dell:~$ cat input.txt 
this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

그리고 스크립트 validator.pl :

tiago@dell:~$ cat validator.pl 
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

sub validate_s0 {
    my $line = $_[0];
    if ( $line =~ \d{5/ ){
        return "true";
    }
    return "false";
}

sub validate_s1 {
    my $line = $_[0];
    if ( $line =~ /\.$/ ){
        return "false";
    }
    return "true";
}

sub validate_s2 {
    my $line = $_[0];
    if ( $line =~ /.*?\(.*\d{5.*?\).*/ ){
        return "false";
    }
    return "true";
}

my $skip = "false";
while (<>){
    my $line = $_; 

    if( $line =~ /if\(/ ){
       $skip = "true";  
    }

    if ( $skip eq "false" ) {
        my $s0_status = validate_s0 "$line"; 
        my $s1_status = validate_s1 "$line";
        my $s2_status = validate_s2 "$line";

        if ( $s0_status eq "true"){
            if ( $s1_status eq "true"){
                if ( $s2_status eq "true"){
                    print "$line";
                }
            }
        }
    } 

    if ( $line =~ /\/\/endif/) {
        $skip="false";
    }
}

실행:

tiago @ dell : ~ $ cat input.txt | 펄 validator.pl
12345와 일치해야합니다.
12345와 일치해야합니다.
12345와 일치해야합니다.

2

이것이 도움이 될지 여부는 확실하지 않지만 다음 가정을 고려하여 솔루션을 제공하고 있습니다.

  1. 모든 조건을 확인하려면 우아한 솔루션이 필요합니다.
  2. 조건은 미래 및 언제든지 변경 될 수 있습니다.
  3. 한 조건이 다른 조건에 의존해서는 안됩니다.

그러나 다음도 고려했습니다.

  1. 주어진 파일에는 최소한의 오류가 있습니다. 그렇다면 내 코드가 이에 대처하기 위해 약간의 수정이 필요할 수 있습니다.
  2. 스택을 사용하여 if(블록을 추적했습니다 .

여기에 해결책이 있습니다.

C #과 함께 MEF (Microsoft Extensibility Framework)를 사용하여 구성 가능한 파서를 구현했습니다. 아이디어는 구문 분석에 단일 파서를 사용하고 구성 가능한 유효성 검사기 클래스 목록을 사용하여 줄을 확인하고 유효성 검사에 따라 true 또는 false를 반환하는 것입니다. 그런 다음 언제든지 유효성 검사기를 추가 또는 제거하거나 원하는 경우 새 유효성 검사기를 추가 할 수 있습니다. 지금까지 말씀 드린 S1, S2 및 S3에 대해 이미 구현했습니다. 포인트 3에서 클래스를 확인하십시오. 나중에 필요하면 s4, s5에 대한 클래스를 추가해야합니다.

  1. 먼저 인터페이스 만들기-

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.Contracts
    {
        public interface IParser
        {
            String[] GetMatchedLines(String filename);
        }
    
        public interface IPatternMatcher
        {
            Boolean IsMatched(String line, Stack<string> stack);
        }
    }
  2. 그런 다음 파일 판독기와 검사기가 나타납니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using FileParserDemo.Contracts;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Collections;
    
    namespace FileParserDemo.Parsers
    {
        public class Parser : IParser
        {
            [ImportMany]
            IEnumerable<Lazy<IPatternMatcher>> parsers;
            private CompositionContainer _container;
    
            public void ComposeParts()
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(IParser).Assembly));
                _container = new CompositionContainer(catalog);
                try
                {
                    this._container.ComposeParts(this);
                }
                catch
                {
    
                }
            }
    
            public String[] GetMatchedLines(String filename)
            {
                var matched = new List<String>();
                var stack = new Stack<string>();
                using (StreamReader sr = File.OpenText(filename))
                {
                    String line = "";
                    while (!sr.EndOfStream)
                    {
                        line = sr.ReadLine();
                        var m = true;
                        foreach(var matcher in this.parsers){
                            m = m && matcher.Value.IsMatched(line, stack);
                        }
                        if (m)
                        {
                            matched.Add(line);
                        }
                     }
                }
                return matched.ToArray();
            }
        }
    }
  3. 그런 다음 개별 검사기가 구현되고 클래스 이름은 자명하므로 더 많은 설명이 필요하지 않다고 생각합니다.

    using FileParserDemo.Contracts;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.PatternMatchers
    {
        [Export(typeof(IPatternMatcher))]
        public class MatchAllNumbers : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\d+");
                return regex.IsMatch(line);
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveIfBlock : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("if\\(");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        //push the if
                        stack.Push(m.ToString());
                    }
                    //ignore current line, and will validate on next line with stack
                    return true;
                }
                regex = new Regex("//endif");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        stack.Pop();
                    }
                }
                return stack.Count == 0; //if stack has an item then ignoring this block
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithEndPeriod : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("(?m)(?!\\d+.*?\\.$)\\d+");
                return regex.IsMatch(line);
            }
        }
    
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithInParenthesis : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\(.*\\d+.*\\)");
                return !regex.IsMatch(line);
            }
        }
    }
  4. 프로그램 -

    using FileParserDemo.Contracts;
    using FileParserDemo.Parsers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var parser = new Parser();
                parser.ComposeParts();
                var matches = parser.GetMatchedLines(Path.GetFullPath("test.txt"));
                foreach (var s in matches)
                {
                    Console.WriteLine(s);
                }
                Console.ReadLine();
            }
        }
    }

테스트를 Test.txt위해 다음 줄이있는 @Tiago의 샘플 파일을 가져 왔습니다.

this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

출력을 제공합니다-

it should match 12345
it should match 12345
it should match 12345

이것이 당신에게 도움이 될지 모르겠지만, 나는 그것을 가지고 노는 즐거운 시간을 보냈습니다 .... :)

그것의 가장 좋은 부분은, 새로운 조건을 추가하기 위해 당신이해야 할 일은의 구현을 제공하는 IPatternMatcher것 뿐이며, 자동으로 호출되어 유효성을 검사한다는 것입니다.


2

@ zx81과 동일 (*SKIP)(*F)하지만 부정적인 예견 어설 션을 사용합니다.

(?m)(?:if\(.*?\/\/endif|\([^()]*\))(*SKIP)(*F)|\b\d+\b(?!.*\.$)

데모

파이썬에서는 이렇게 쉽게 할 수 있습니다.

import re
string = """cat 123 sat.
I like 000 not (456) though 111 is fine
222 if(  //endif if(cat==789 stuff  //endif   333"""
for line in string.split('\n'):                                  # Split the input according to the `\n` character and then iterate over the parts.
    if not line.endswith('.'):                                   # Don't consider the part which ends with a dot.
        for i in re.split(r'\([^()]*\)|if\(.*?//endif', line):   # Again split the part by brackets or if condition which endswith `//endif` and then iterate over the inner parts.
            for j in re.findall(r'\b\d+\b', i):                  # Then find all the numbers which are present inside the inner parts and then loop through the fetched numbers.
                print(j)                                         # Prints the number one ny one.

산출:

000
111
222
333
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.