SED 정규식과 비탐욕적 일치(Perl의 .*? 에뮬레이트)

SED 정규식과 비탐욕적 일치(Perl의 .*? 에뮬레이트)

첫 번째 문자열 과 두 번째 문자열 사이의 sed문자열을 바꾸는 데 사용하고 싶습니다.AB첫 번째발생 AC(포함) XXX.

~을 위한, 다음 문자열이 있습니다(이 문자열은 테스트용으로만 사용됩니다).

ssABteAstACABnnACss

나는 다음과 유사한 출력을 원합니다: ssXXXABnnACss.


나는 다음을 사용하여 이 작업을 수행했습니다 perl.

$ echo 'ssABteAstACABnnACss' | perl -pe 's/AB.*?AC/XXX/'
ssXXXABnnACss

그러나 나는 그것을 달성하기 위해 그것을 사용하고 싶습니다 sed. 다음(Perl 호환 정규식 사용)은 작동하지 않습니다.

$ echo 'ssABteAstACABnnACss' | sed -re 's/AB.*?AC/XXX/'
ssXXXss

답변1

Sed 정규식은 가장 긴 일치 항목과 일치합니다. Sed는 non-greedy와 동등한 기능이 없습니다.

우리가 해야 할 일은 일치하는 것

  1. AB,
    이어서
  2. 제외한 모든 AC수량
  3. AC

불행히도 sed#2는 수행할 수 없습니다. 적어도 다중 문자 정규 표현식에서는 수행할 수 없습니다. 물론, 단일 문자 정규식 @( )의 경우 이 또는 를 [123]수행할 수 있습니다 . 따라서 sed 의 모든 항목을 변경한 다음 검색하여 sed의 제한 사항을 해결할 수 있습니다.[^@]*[^123]*AC@

  1. AB,
    이어서
  2. @제외한
    모든 것
  3. @

이와 같이:

sed 's/AC/@/g; s/AB[^@]*@/XXX/; s/@/AC/g'

마지막 부분은 일치하지 않는 @백 인스턴스를 AC.

그러나 입력 내용에 이미 문자가 포함되어 있을 수 있으므로 이는 무모한 접근 방식입니다 @. 그래서 그것들을 일치시킴으로써 우리는 거짓 긍정을 얻을 수 있습니다. 그러나 NUL() 문자는 쉘 변수에 포함되지 않으므로 \x00NUL은 위의 해결 방법에서 대신 사용하기에 좋은 문자일 수 있습니다 @.

$ echo 'ssABteAstACABnnACss' | sed 's/AC/\x00/g; s/AB[^\x00]*\x00/XXX/; s/\x00/AC/g'
ssXXXABnnACss

NUL을 사용하려면 GNU sed가 필요합니다. (GNU 기능이 활성화되도록 하려면 사용자가 쉘 변수 POSIXLY_CORRECT를 설정하면 안 됩니다.)

-zNUL로 구분된 입력(예: 출력)을 처리하기 위해 GNU 플래그와 함께 sed를 사용하는 경우 find ... -print0NUL은 패턴 공간에 나타나지 않으며 여기서 NUL은 대체하기에 좋은 선택입니다.

printfNUL은 bash 변수에 나타날 수 없지만 명령 에는 포함될 수 있습니다 . 입력 문자열에 NUL을 포함한 모든 문자가 포함될 수 있는 경우 다음을 참조하세요.Stefan Chazeras의 답변이것은 깔끔한 이스케이프 방법을 추가합니다.

답변2

비탐욕적 일치 수행단일 문자, 일치를 종료하는 문자를 제외한 모든 문자와 일치합니다.

그리디 매칭:

$ echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

탐욕스럽지 않은 매칭:

$ echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar

원천:sed - Christoph Sieghart의 욕심 없는 매칭

답변3

일부 sed구현에서는 이를 지원합니다.ssedPCRE 모드가 있습니다:

ssed -R 's/AB.*?AC/XXX/'

AT&T AST sed*?탐욕스럽지 않은 버전 으로 연산자를 지원합니다.*확장하다(와 함께 -E) 및향상된( -A정규식 사용).

sed -E 's/AB.*?AC/XXX/'
sed -A 's/AB.*?AC/XXX/'

이 구현 및 보다 일반적으로 해당 -E/ -A패턴에서는 Perl과 유사한 정규 표현식을 내부적으로 사용할 수 있습니다 (?P:perl-like regexp here). 그러나 위에 표시된 것처럼 연산자에는 이것이 필요하지 않습니다 *?.

그것은향상된정규식에는 결합 연산자와 부정 연산자도 있습니다.

sed -A 's/AB(.*&(.*AC.*)!)AC/XXX/'

이식 가능한 방법은 다음 기술을 사용할 수 있습니다. AC끝 문자열(예: 여기)을 시작 또는 끝 문자열(예: 여기)에 나타나지 않는 단일 문자로 바꾸면 :그렇게 할 수 있습니다 . s/AB[^:]*://시작 및 끝 문자열과 충돌하지 않는 이스케이프 메커니즘을 사용하여 입력에 나타납니다.

한 가지 예:

sed 's/_/_u/g; # use _ as the escape character, escape it
     s/:/_c/g; # escape our replacement character
     s/AC/:/g; # replace the end string
     s/AB[^:]*:/XXX/; # actual replacement
     s/:/AC/g; # restore the remaining end strings
     s/_c/:/g; # revert escaping
     s/_u/_/g'

GNU의 경우 sed한 가지 접근 방식은 개행 문자를 대체 문자로 사용하는 것입니다. 한 번에 한 줄씩 처리하기 때문에 sed패턴 공간에 개행 문자가 나타나지 않으므로 다음과 같이 할 수 있습니다.

sed 's/AC/\n/g;s/AB[^\n]*\n/XXX/;s/\n/AC/g'

sed이는 일반적으로 다른 구현에서는 지원하지 않기 때문에 작동하지 않습니다 [^\n]. GNU의 경우 sedPOSIX 호환성이 활성화되지 않았는지 확인해야 합니다(예: POSIXLY_CORRECT 환경 변수 사용).

답변4

해결책은 매우 간단합니다. .*욕심을 가지되 완전히 욕심을 부리지는 마세요. ssABteAstACABnnACssregexp 와 일치하는 것을 고려하세요 AB.*AC. AC다음 내용은 실제로 .*일치해야 합니다. 문제는 욕심 때문에 .*후속 AC경기가마지막 AC첫 번째보다는. 정규식의 리터럴이 ssABteAstACABnn의 마지막 리터럴과 일치하는 동안 .*첫 번째 것을 먹습니다 .ACAC교류봄 여름 시즌. 이런 일이 발생하지 않도록 하려면 AC첫 번째 것을 다른 것으로 바꾸십시오.말도 안 되는두 번째 것과 다른 모든 것을 구별하십시오.

echo ssABteAstACABnnACss | sed 's/AC/-foobar-/; s/AB.*-foobar-/XXX/'
ssXXXABnnACss

.*이것과 정규식 외에는 아무것도 없기 때문에 탐욕은 이제 in의 발밑에서 멈출 것입니다 .-foobar-ssABteAst-foobar-ABnnACss-foobar--foobar--foobar- ~ 해야 하다게임을 하자. 이전 문제는 정규 표현식 에 두 개의 일치 항목이 있었는데 욕심 AC때문에 마지막 일치 항목이 선택되었다는 것이었습니다. 그러나 의 경우 단 한 번의 일치만이 가능하며, 이 일치는 이것이 절대 욕심이 아니라는 것을 증명합니다. 버스 정류장은 다음 지역에서만 나타납니다..*AC-foobar-.*.*하나다음 정규식의 나머지 부분은 여전히 ​​일치합니다 .*.

ACAB오류가 AC대체되므로 이 해결 방법이 첫 번째 해결 방법보다 먼저 나타나면 실패할 것입니다 -foobar-. 예를 들어, 첫 번째 대체 sed후에 ACssABteAstACABnnACss는 가 됩니다 -foobar-ssABteAstACABnnACss. 따라서 일치하는 항목을 찾을 수 없습니다 AB.*-foobar-. 그러나 시퀀스가 ​​항상...AB...AC...AB...AC...인 경우 이 솔루션은 성공합니다.

관련 정보