Programming

sed에서 욕심 많은 (reluctant) 정규 표현식 일치?

procodes 2020. 2. 23. 12:05
반응형

sed에서 욕심 많은 (reluctant) 정규 표현식 일치?


sed를 사용하여 URL 줄을 정리하여 도메인 만 추출하려고합니다.

그래서 :

http://www.suepearson.co.uk/product/174/71/3816/

내가 원하는:

http://www.suepearson.co.uk/

(훈련 슬래시 유무에 관계없이 중요하지 않습니다)

나는 시도했다 :

 sed 's|\(http:\/\/.*?\/\).*|\1|'

및 (욕심없는 정량자를 탈출)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

그러나 나는 욕심없는 정량 자 ( ?)를 작동시킬 수 없으므로 항상 전체 문자열과 일치하게됩니다.


기본적이거나 확장 된 Posix / GNU 정규식은 욕심없는 정량자를 인식하지 못합니다. 나중에 정규식이 필요합니다. 다행히도이 컨텍스트에 대한 Perl 정규식은 다음과 같이 쉽게 얻을 수 있습니다.

perl -pe 's|(http://.*?/).*|\1|'

이 특정 경우에는 욕심없는 정규 표현식을 사용하지 않고도 작업을 수행 할 수 있습니다.

이 욕심없는 정규식 [^/]*대신 다음을 시도하십시오 .*?.

sed 's|\(http://[^/]*/\).*|\1|g'

sed를 사용하면 일반적으로 구분 기호까지 구분 기호를 제외한 모든 항목을 검색하여 욕심없는 검색을 구현합니다.

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

산출:

http://www.suon.co.uk

이것은:

  • 출력하지 않습니다 -n
  • 검색, 패턴 일치, 교체 및 인쇄 s/<pattern>/<replace>/p
  • 사용하는 ;검색 명령 구분을 대신 /하므로 입력하기 쉽도록하기 위해s;<pattern>;<replace>;p
  • 괄호 사이의 일치를 기억하십시오 \(... \), 나중에 \1, \2...
  • 시합 http://
  • 괄호 안에 아무것도 다음에 [], [ab/]의미 중 하나 a또는 b또는/
  • 처음 ^[]수단 not, 그래서 아무것도 뒤에 만의 것[]
  • 그래서 [^/]제외하고는 아무것도 의미 /의 문자를
  • *이전 그룹을 반복하는 것이므로를 [^/]*제외한 문자를 의미합니다 /.
  • 지금까지는 sed -n 's;\(http://[^/]*\)검색하고 기억하고 http://뒤에 /찾은 것을 제외한 모든 문자를 의미합니다.
  • 우리는 도메인의 끝까지 검색하고 싶기 때문에 다음에 멈추고 끝에 /다른 것을 추가 하고 싶습니다 /: sed -n 's;\(http://[^/]*\)/'그러나 우리는 도메인 다음의 나머지 줄과 일치시키기를 원합니다..*
  • 이제 그룹 1 ( \1) 에서 기억 된 일치 는 도메인이므로 일치하는 행을 그룹에 저장된 내용으로 바꾸고 \1인쇄하십시오.sed -n 's;\(http://[^/]*\)/.*;\1;p'

도메인 뒤에 백 슬래시를 포함 시키려면 그룹에 백 슬래시를 하나 더 추가하여 기억하십시오.

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

산출:

http://www.suon.co.uk/

sed는 "욕심없는"연산자를 지원하지 않습니다.

일치에서 "/"를 제외 시키려면 "[]"연산자를 사용해야합니다.

sed 's,\(http://[^/]*\)/.*,\1,'

PS "/"를 백 슬래시 할 필요가 없습니다.


에 게으른 (심지어) 수량화 시뮬레이션 sed

그리고 다른 모든 정규식 맛!

  1. 식의 첫 항목 찾기 :

    • POSIX ERE ( -r옵션 사용 )

      정규식 :

      (EXPRESSION).*|.
      

      sed :

      sed -r "s/(EXPRESSION).*|./\1/g" # Global `g` modifier should be on
      

      예 (첫 번째 숫자 찾기) 라이브 데모 :

      $ sed -r "s/([0-9]+).*|./\1/g" <<< "foo 12 bar 34"
      
      12
      

      어떻게 작동 합니까?

      이 정규 표현식은 교대로부터 이익을 얻는다 |. 각 위치에서 엔진은 교대의 첫 번째면 (우리의 목표)을 찾고, 교대의 두 번째면이 일치하지 않으면 .다음 즉시 문자와 일치 하는 점이 있습니다.

      여기에 이미지 설명을 입력하십시오

      글로벌 플래그가 설정되었으므로 엔진은 입력 문자열 또는 대상의 끝까지 문자별로 문자를 계속 일치시킵니다. 교대 왼쪽의 첫 번째이자 유일한 캡처 그룹이 일치 (EXPRESSION)하자마자 나머지 라인도 즉시 소비됩니다 .*. 우리는 이제 첫 번째 캡처 그룹에서 가치를 유지합니다.

    • POSIX BRE

      정규식 :

      \(\(\(EXPRESSION\).*\)*.\)*
      

      sed :

      sed "s/\(\(\(EXPRESSION\).*\)*.\)*/\3/"
      

      예 (첫 번째 일련의 숫자 찾기) :

      $ sed "s/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/" <<< "foo 12 bar 34"
      
      12
      

      이 버전은 ERE 버전과 유사하지만 변경 사항이 없습니다. 그게 다야. 각 단일 위치에서 엔진은 숫자를 일치 시키려고합니다.

      여기에 이미지 설명을 입력하십시오

      그것이 발견되면, 다른 다음 숫자가 소비되고 캡처되고 나머지 줄은 즉시 일치합니다. 그렇지 않으면 더 많거나 0을* 의미 하기 때문에 두 번째 캡처 그룹을 건너 뛰고 \(\([0-9]\{1,\}\).*\)*.하나에 도달 하여 단일 문자와 일치 하며이 프로세스는 계속됩니다.

  2. 구분 된 표현식 의 첫 항목 찾기 :

    이 방법은 구분 된 문자열의 첫 항목과 일치합니다. 이것을 문자열 블록이라고 부를 수 있습니다.

    sed "s/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g"
    

    입력 문자열 :

    foobar start block #1 end barfoo start block #2 end
    

    -EDE : end

    -SDE : start

    $ sed "s/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g"
    

    산출:

    start block #1 end
    

    첫 번째 정규 표현식 \(end\).*은 첫 번째 구분 기호를 일치시키고 캡처하며, 마지막 구분 기호 인 end최근 캡처 된 문자로 모든 일치를 대체합니다. 이 단계에서 출력은 다음과 같습니다 foobar start block #1 end.

    여기에 이미지 설명을 입력하십시오

    그런 다음 \(\(start.*\)*.\)*위의 POSIX BRE 버전과 동일한 두 번째 정규식으로 결과가 전달됩니다 . 시작 구분 기호 start가 일치하지 않으면 단일 문자 와 일치하고 그렇지 않으면 시작 구분 기호와 일치하고 캡처하고 나머지 문자와 일치합니다.

    여기에 이미지 설명을 입력하십시오


질문에 직접 대답

접근법 # 2 (구분 된 표현식)를 사용하여 두 가지 적절한 표현식을 선택해야합니다.

  • EDE : [^:/]\/

  • SDE : http:

용법:

$ sed "s/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/" <<< "http://www.suepearson.co.uk/product/174/71/3816/"

산출:

http://www.suepearson.co.uk/

하나 이상의 문자에 대한 욕심없는 해결책

이 스레드는 실제로 오래되었지만 사람들이 여전히 필요하다고 생각합니다. 의 첫 번째 발생까지 모든 것을 죽이고 싶다고 가정 해 봅시다 HELLO. 당신은 말할 수 없습니다 [^HELLO]...

따라서 좋은 해결책은 입력에서 예상하지 않은 고유 한 단어를 절약 할 수 있다고 가정하면 두 단계로 구성됩니다 top_sekrit.

이 경우 다음을 수행 할 수 있습니다.

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

물론 더 간단한 입력으로 더 작은 단어를 사용하거나 단일 문자를 사용할 수도 있습니다.

HTH!


이것은 cut을 사용하여 수행 할 수 있습니다.

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3

sed-Christoph Sieghart의 욕심없는 매칭

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

욕심 매칭

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

욕심없는 매칭

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

정규식을 사용하지 않는 또 다른 방법은 필드 / 구분 기호 방법을 사용하는 것입니다.

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"

sed 확실히 그 자리가 있지만 이것은 그들 중 하나가 아닙니다!

Dee가 지적했듯이 : 그냥 사용하십시오 cut. 이 경우 훨씬 간단하고 안전합니다. 다음은 Bash 구문을 사용하여 URL에서 다양한 구성 요소를 추출하는 예입니다.

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

당신에게 제공합니다 :

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

보다시피, 이것은 훨씬 더 유연한 접근법입니다.

(디에게 모든 크레딧)


순수한 sed를 사용 하여이 문제를 해결하기를 희망합니다. 이것이 일반적인 해결책은 아니지만 "루프"를 사용하여 다음과 같이 문자열의 불필요한 부분을 모두 제거 할 수 있습니다.

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r : 확장 정규식 사용 (+ 및 이스케이프 처리되지 않은 괄호)
  • ": loop": "loop"라는 새 레이블을 정의하십시오.
  • -e : sed에 명령 추가
  • "t loop": 대체가 성공한 경우 "loop"레이블로 다시 이동

여기서 유일한 문제는 마지막 구분 문자 ( '/')도 자르는 것이지만 실제로 필요한 경우 "루프"가 끝난 후에도 간단히 다시 넣을 수 있습니다. 이전 명령 끝에 추가 명령을 추가하십시오. 명령 줄 :

-e "s,$,/,"

sed 's|(http:\/\/[^\/]+\/).*|\1|'

sed -E는 정규식을 확장 (현대) 정규식으로 해석합니다.

업데이트 : MacOS X의 경우 -E, GNU sed의 경우 -r


펄, 컷 등 대신에 sed를 사용하려한다고 명시 했으므로 그룹화를 시도하십시오. 이로 인해 탐욕스럽지 않은 식별자가 인식되지 않을 수 있습니다. 첫 번째 그룹은 프로토콜입니다 (예 : 'http : //', 'https : //', 'tcp : //'등). 두 번째 그룹은 도메인입니다.

에코 "http://www.suon.co.uk/product/1/7/3/"| sed "s | ^ \ (. * // \) \ ([^ /] * \). * $ | \ 1 \ 2 |"

그룹화에 익숙하지 않은 경우 여기 에서 시작 하십시오 .


나는 이것이 오래된 항목이라는 것을 알고 있지만 누군가가 유용하다고 생각할 수 있습니다. 전체 도메인 이름은 총 253자를 초과 할 수 없으므로. *를. \ {1, 255 \}로 바꾸십시오.


이것은 sed를 사용하여 다중 문자 문자열을 욕심스럽지 않게 일치시키는 방법입니다. 당신이 모든을 변경하고 싶은 말은하자 foo...bar<foo...bar>너무 예를 들어이 입력 :

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

이 출력이되어야합니다 :

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

그렇게하려면 foo와 bar를 개별 문자로 변환 한 다음 그 문자의 부정을 사용하십시오.

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

위 :

  1. s/@/@A/g; s/{/@B/g; s/}/@C/g변환되고 {그리고 }그 후, 문자를 변환 가능하도록 입력에 존재할 수없는 자리 문자열로 foo하고 bar것이다.
  2. s/foo/{/g; s/bar/}/g변환된다 foobar{그리고 }각각
  3. s/{[^{}]*}/<&>/g변환 - 우리가 원하는 연산을 수행 foo...bar<foo...bar>
  4. s/}/bar/g; s/{/foo/g변환된다 {하고 }다시 foobar.
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g 자리 표시 자 문자열을 다시 원래 문자로 변환합니다.

위의 내용은 첫 번째 단계에서 이러한 문자열을 제조 할 때 입력에 존재하지 않는 특정 문자열에 의존하지 않으며 {[^{}]*}필요한만큼 여러 번 사용할 수 있으므로 일치하는 특정 정규 표현식의 발생을 신경 쓰지 않습니다. 표현식에서 원하는 실제 일치 및 / 또는 seds 숫자 일치 연산자를 분리합니다 (예 : 두 번째 항목 만 대체).

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV

아직이 답을 보지 못했다, 그래서 여기에 당신이 할 수있는 방법 vi이나 vim:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

이렇게하면 vi :%s전역 적으로 대체 (후행 g) 가 실행되고 패턴을 찾을 수없는 경우 오류가 발생하지 않도록하고 ( e) 결과 변경 사항을 디스크에 저장하고 종료합니다. 이렇게 &>/dev/null하면 GUI가 화면에서 잠깐 동안 깜박이지 않아 성 가실 수 있습니다.

나는 vi(1) perl이 죽어 가고, (2) vim은 매우 고급 정규식 엔진을 가지고 있으며, (3) 이미 vi일상적인 사용법 편집에서 정규식에 친숙 합니다. 서류.


echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

귀찮게, 나는 다른 포럼에서 그것을 얻었다 :)


sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1| 너무 작동


다른 sed 버전 :

sed 's|/[:alphanum:].*||' file.txt

그것은 일치 /영숫자 문자 (그렇게하지 다른 슬래시)뿐만 아니라 라인의 끝까지 문자의 나머지 하였다. 그 후에는 아무것도 대체하지 않습니다 (즉, 삭제합니다).


다음은 2 단계 접근 방식과 awk로 수행 할 수있는 작업입니다.

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

출력 : http://www.suepearson.co.uk

희망이 도움이됩니다!

참고 URL : https://stackoverflow.com/questions/1103149/non-greedy-reluctant-regex-matching-in-sed



반응형