SED 정규식과 일치하지 않는 일치 (Perl의. *?)


22

내가 사용하고자하는 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

2
이것은 말이되지 않습니다. Perl에 작동하는 솔루션이 있지만 Sed를 사용하고 싶은 이유는 무엇입니까?
Kusalananda

답변:


16

Sed 정규식은 가장 긴 일치 항목과 일치합니다. Sed는 욕심없는 것과 동등하지 않습니다.

분명히 우리가하고 싶은 것은 일치입니다

  1. AB,
    뒤에
  2. 이외의 금액 AC,
    다음
  3. AC

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

  1. AB,
    뒤에
  2. 이외의 임의의 수의 @,
    다음에
  3. @

이처럼 :

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

마지막 부분은 일치하지 않는 인스턴스를 @로 변경합니다 AC.

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

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

NUL을 사용하려면 GNU sed가 필요합니다. (GNU 기능을 사용하려면 사용자가 쉘 변수 POSIXLY_CORRECT를 설정하지 않아야합니다.)

GNU의 -z플래그 와 함께 sed를 사용하여 의 출력과 같이 NUL로 분리 된 입력을 처리하는 경우 NUL find ... -print0은 패턴 공간에 없으며 여기서 NUL은 대체를위한 좋은 선택입니다.

NUL은 bash 변수에 속할 수 없지만 printf명령 에 포함시킬 수 있습니다 . 입력 문자열에 NUL을 포함한 모든 문자가 포함될 수 있다면 Stéphane Chazelas의 답변 을 참조하십시오 .


자세한 설명을 추가하기 위해 답변을 편집했습니다. 자유롭게 다듬거나 롤백하십시오.
G-남자 '는 분석 재개 모니카'말한다

@ G-Man 훌륭한 설명입니다! 아주 잘 했어요 감사합니다.
John1024

당신은 할 수 echo또는 printfbash는 잘`\ 000 '(또는 입력 파일에서 올 수). 그러나 일반적으로 텍스트 문자열에는 NUL이 없을 수 있습니다.
ilkkachu

@ilkkachu 당신이 맞습니다. 내가 작성한 것은 쉘 변수 또는 매개 변수에 NUL을 포함 할 수 없다는 것 입니다. 답변이 업데이트되었습니다.
John1024

당신이 변경되었을 경우이 훨씬 더 안전하지 않을까요 ACAC@다시와?
Michael Vehrs

7

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

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

AT & T ast sed정규 표현식을 사용할 때 관련과 부정을 가지고 있습니다 .

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

아마도이 기술을 사용할 수 있습니다. 끝 문자열 (here 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/g; # 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/g;s/\n/AC/g'

sed지원하지 않기 때문에 다른 구현 에서는 일반적으로 작동 하지 않습니다 [^\n]. GNU sed를 사용하면 POSIXLY_CORRECT 환경 변수와 같이 POSIX 호환성이 활성화되어 있지 않은지 확인해야합니다.


6

아니요, sed 정규 표현식은 욕심이 일치하지 않습니다.

Perl과 동일하게 “ AC포함하지 않는 항목 ”을 사용하여 모든 텍스트를 첫 번째 항목까지 일치시킬 수 있습니다 . “포함하지 않는 것은” 정규 표현식으로 쉽게 표현할 수 없습니다. 정규 표현식의 부정을 인식하는 정규 표현식이 항상 있지만 부정 정규 표현식은 빠르게 복잡해집니다. 휴대용 sed에서는 부정 정규 표현식이 확장 정규 표현식 (예 : awk)으로 표시되지만 이식 가능한 기본 정규 표현식에는없는 대체를 그룹화해야하기 때문에 전혀 불가능합니다. GNU sed와 같은 일부 sed 버전에는 가능한 모든 정규 표현식을 표현할 수있는 BRE 확장 기능이 있습니다.ACAC.*?ACAC

sed 's/AB\([^A]*\|A[^C]\)*A*AC/XXX/'

정규식을 부정하기가 어렵 기 때문에 일반화되지 않습니다. 대신 할 수있는 것은 일시적으로 선을 변환하는 것입니다. 일부 sed 구현에서는 줄 바꿈을 입력 줄에 표시 할 수 없으므로 줄 바꿈을 마커로 사용할 수 있습니다 (여러 마커가 필요한 경우 줄 바꿈과 다양한 문자 사용).

sed -e 's/AC/\
&/g' -e 's/AB[^\
]*\nAC/XXX/' -e 's/\n//g'

그러나 일부 sed 버전의 문자 집합에서는 백 슬래시 줄 바꿈이 작동하지 않습니다. 특히 이것은 GNU sed에서 작동하지 않습니다. GNU sed는 내장되지 않은 Linux에서의 sed 구현입니다. GNU sed에서는 \n대신 사용할 수 있습니다 .

sed -e 's/AC/\
&/g' -e 's/AB[^\n]*\nAC/XXX/' -e 's/\n//g'

이 특정 경우 첫 번째 AC줄 바꿈으로 바꾸면 충분합니다 . 위에서 제시 한 접근 방식이 더 일반적입니다.

sed에서보다 강력한 접근 방식은 선을 보류 공간에 저장하고, 선의 첫 번째 "관심있는"부분을 제외한 모든 부분을 제거하고, 보류 공간과 패턴 공간을 교환하거나 패턴 공간을 보류 공간에 추가하고 반복하는 것입니다. 그러나 이렇게 복잡한 작업을 시작하면 실제로 awk로 전환하는 것을 고려해야합니다. Awk에는 욕심없는 일치가 없지만 문자열을 분할하고 부품을 변수에 저장할 수 있습니다.


@ilkkachu 아니오, 그렇지 않습니다. s/\n//g모든 줄 바꿈을 제거합니다.
Gilles 'SO- 악한 중지'

asdf. 그래, 내 나쁜
ilkkachu

3

sed-Christoph Sieghart의 욕심없는 매칭

sed에서 욕심없는 일치를 얻는 트릭은 일치하는 문자를 제외하고 모든 문자를 일치시키는 것입니다. 나도 알다시피, 그러나 그것은 귀중한 시간을 낭비하고 쉘 스크립트는 결국 빠르고 쉬워야합니다. 따라서 다른 사람이 필요할 수있는 경우 :

욕심 매칭

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

욕심없는 매칭

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


3
“두뇌가없는”이라는 용어는 모호합니다. 이 경우, 귀하 (또는 Christoph Sieghart)가이를 통해 생각한 것이 확실하지 않습니다. 당신이 (제로의-더 외 발현이 다음에 질문의 특정 문제를 해결하는 방법을 보여했다면 특히, 좋았을 것 으로 하나 개 이상의 문자 ) . 이 경우이 답변이 제대로 작동하지 않을 수 있습니다.
Scott

토끼 구멍은 언뜻보기에 나보다 훨씬 깊습니다. 다중 문자 정규식에는이 해결 방법이 제대로 작동하지 않습니다.
gresolio

0

귀하의 경우 다음과 같이 닫는 문자를 무효화 할 수 있습니다.

echo 'ssABteAstACABnnACss' | sed 's/AB[^C]*AC/XXX/'

2
질문은 "내가 처음 간 것도 대체 할 말합니다 AB와의 첫 번째 항목 AC과를 XXX..."하고 있습니다 ssABteAstACABnnACssint로서 예를 들어, 입력. 이 답변은 해당 예 에서 작동 하지만 일반적인 질문에는 대답하지 않습니다. 예를 들어, ssABteCstACABnnACssoutput도 산출해야 aaXXXABnnACss하지만 명령은이 행을 변경없이 통과시킵니다.
G-Man, 'Reinstate

0

해결책은 매우 간단합니다. .*탐욕 스럽지만 절대적으로 탐욕 스럽지는 않습니다. ssABteAstACABnnACss정규 표현식과 일치하는 것을 고려하십시오 AB.*AC. AC다음이 .*필수 실제로 경기가 있습니다. 문제는 .*탐욕 스럽기 때문에 후속 AC은 첫 번째가 아닌 마지막 과 일치 AC한다는 것입니다. regexp 의 리터럴 이 ssABteAstACABnn AC ss 의 마지막 리터럴 과 일치하는 동안 .*첫 번째 AC를 먹습니다 . 이를 방지하려면 첫 번째 대체 뭔가 말도 두 번째에서와 다른 어떤에서 그것을 차별화.ACAC

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

욕심은 .*지금의 기슭에 중지 -foobar-에서 ssABteAst-foobar-ABnnACss다른 없기 때문에 -foobar-이것보다 -foobar-, 그리고 정규 표현식은 -foobar- 반드시 일치해야합니다. 이전 문제는 정규 표현식 AC에 두 개의 일치 항목이 있었지만 .*욕심 때문에 마지막 일치 항목 AC이 선택되었습니다. 그러나을 사용 -foobar-하면 하나의 일치 만 가능하며이 일치는 .*완전히 욕심이 아님을 증명합니다 . 버스 정류소는 다음 정규 표현식 나머지에 대해 하나의 일치 항목 .*만 남아 있는 경우 발생합니다 ..*

이 경우이 솔루션은 실패 할주의 AC첫 번째 전에 나타나는 AB잘못이 있기 때문 AC으로 대체됩니다 -foobar-. 예를 들어, 첫 번째 후 sed치환 ACssABteAstACABnnACss된다 -foobar-ssABteAstACABnnACss; 따라서에 대해 일치하는 항목을 찾을 수 없습니다 AB.*-foobar-. 그러나 시퀀스가 ​​항상 ... AB ... AC ... AB ... AC ...이면이 솔루션이 성공합니다.


0

하나 개의 대안은, 그래서 문자열을 변경하는 것입니다 하려는 욕심 경기

echo "ssABtCeCAstACABnnACss" | rev | sed -E "s/(.*)CA.*BA(.*)/\1CA+-+-+-+-BA\2/" | rev

사용 rev하여 일치 기준, 사용 역, 문자열을 반대로 sed.... 결과를 반대로 다음 일반적인 방식에서

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