정규식을 통해 파일에서 여러 줄을 얻는 방법은 무엇입니까?

정규식을 통해 파일에서 여러 줄을 얻는 방법은 무엇입니까?

정규식을 통해 파일에서 여러 줄을 얻는 방법은 무엇입니까?

나는 종종 정규 표현식을 통해 여러 줄을 가져오거나 수정하고 싶습니다. 예시 사례:

XML/SGML 파일의 일부를 읽으려고 합니다. 형식이 올바르거나 예측 가능한 구문일 필요는 없으므로 정규식은 적절한 파서보다 안전합니다. 또한 이 작업을 완전히 수행할 수 있기를 바랍니다. 몇 가지 키워드만 알려진 쉘 스크립트(Solaris 및 Linux에서 실행되는 구조화되지 않은 파일)에서.

XML 예:

<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>

<tag1>여기에서 어딘가에 포함되어 있는지 읽고 싶습니다 foo.

이와 같은 정규식은 (<tag1>.*?foo.*?</tag1>)올바른 부분을 제공해야 하지만 grep및 같은 도구는 sed한 줄에서만 작동합니다. 어떻게 얻을 수 있나요?

<tag1>
 <tag2>foo</tag2>
</tag1>

이 경우?

답변1

GNU grep이 설치되어 있는 경우 -P(perl-regex) 플래그를 전달하고 활성화하여 PCRE_DOTALL여러 줄 검색을 수행 할 수 있습니다.(?s)

grep -oP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt
<tag1>
<tag2>foo</tag2>
</tag1>

위의 방법이 플랫폼에서 작동하지 않으면 -zgrep이 NUL을 줄 구분 기호로 처리하여 전체 파일이 한 줄처럼 보이도록 하는 플래그를 추가로 전달해 보십시오.

grep -ozP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt

답변2

#begin command block
#append all lines between two addresses to hold space 
    sed -n -f - <<\SCRIPT file.xml
        \|<tag1>|,\|</tag1>|{ H 
#at last line of search block exchange hold and pattern space 
            \|</tag1>|{ x
#if not conditional ;  clear buffer ; branch to script end
                \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
#do work ; print result; clear buffer ; close blocks
    s?*?*?;p;s/.*//;h;b}}
SCRIPT

위의 작업을 수행하는 경우 표시되는 데이터를 고려하여 마지막 깨끗한 줄 앞에 sed다음과 같은 패턴 공간을 사용해야 합니다.

 ^\n<tag1>\n<tag2>foo</tag2>\n</tag1>$

look를 사용하여 언제든지 패턴 공간을 인쇄 할 수 있습니다 . 그런 다음 \n문자의 주소를 지정할 수 있습니다 .

sed l <file

sed이를 처리하기 위해 호출되는 단계를 각 라인에서 보여줍니다 l.

\backslash그래서 방금 테스트했는데, 첫 번째 줄 이후에 한 번 더 필요 ,comma하지만 그 외에는 그대로 작동합니다. _sed_function데모 목적으로 답변 전체에서 쉽게 호출할 수 있도록 여기에 넣었습니다 .(설명이 포함되어 있지만 간결성을 위해 여기에서는 삭제했습니다.)

_sed_function() { sed -n -f /dev/fd/3 
} 3<<\SCRIPT <<\FILE 
    \|<tag1>|,\|</tag1>|{ H
        \|</tag1>|{ x
            \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
    s?*?*?;p;s/.*//;h;b}}
#END
SCRIPT
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
FILE


_sed_function
#OUTPUT#
<tag1>
 <tag2>foo</tag2>
</tag1>

이제 스크립트를 개발하면서 작업 중인 내용을 확인할 수 있도록 전환하고 비작업 데모를 제거하여 p마지막 줄 은 다음과 같습니다.ls?sed 3<<\SCRIPT

l;s/.*//;h;b}}

그런 다음 다시 실행합니다.

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

좋아요! 그래서 내 말이 맞았다. 기분이 좋았다. 이제 l가져왔지만 삭제된 행을 무작위로 살펴보겠습니다 . 현재 항목을 삭제 l하고 여기에 하나를 추가하면 !{block}다음과 같습니다.

!{l;s/.*//;h;b}

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$

죽이기 전의 상황은 이렇습니다.

마지막으로 보여드리고 싶은 것은 H건축 당시의 오래된 공간입니다. 몇 가지 핵심 개념을 보여드리고 싶습니다. 그래서 다시 마지막 look을 제거하고 첫 번째 줄을 변경하여 H끝에 이전 공간의 보기를 추가합니다.

{ H ; x ; l ; x

_sed_function
#OUTPUT#
\n<tag1>$
\n<tag1>\n <tag2>bar</tag2>$
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$
\n<tag1>$
\n<tag1>\n <tag2>foo</tag2>$
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

H오래된 공간살아남았다라인 루프 - 따라서 이름입니다. 그렇다면 사람들은 어떤 실수를 자주 저지르나요? 음, 무엇입니까?자주 발생하는 문제는 사용 후 제거해야 한다는 것입니다. 이 경우에는 x한 번만 변경하므로 공간을 유지하십시오.~이 되다패턴 공간과 그 반대의 경우에도 이 변화는라인 사이클을 견딜 수 있습니다.

결과적으로 내 패턴 공간이었던 예약된 공간을 삭제해야 합니다. 먼저 다음을 사용하여 현재 패턴 공간을 지웁니다.

s/.*//

그냥 각 문자를 선택하고 삭제합니다. d현재 라인 사이클이 종료되고 다음 명령이 완료되지 않아 스크립트가 거의 중단되기 때문에 사용할 수 없습니다 .

h

이는 비슷한 방식으로 작동 H하지만덮어쓰기예약된 공간이므로 예약된 공간 위에 빈 패턴 공간을 복사하여 효과적으로 제거했습니다. 이제 다음을 수행할 수 있습니다.

b

나가.

이것이 sed제가 스크립트를 작성하는 방법입니다.

답변3

@jamespfinn의 답변은 파일이 예제만큼 간단하다면 훌륭하게 작동합니다. <tag1>2줄 이상에 걸쳐 있는 상황이 더 복잡하다면 약간 더 복잡한 트릭이 필요합니다. 예를 들어:

$ cat foo.xml
<tag1>
 <tag2>bar</tag2>
 <tag3>baz</tag3>
</tag1>
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>
$ perl -ne 'if(/<tag1>/){$a=1;} 
            if($a==1){push @l,$_}
            if(/<\/tag1>/){
              if(grep {/foo/} @l){print "@l";}
               $a=0; @l=()
            }' foo.xml
<tag1>

  <tag2>foo</tag2>
 </tag1>
<tag1>
  <tag2>bar</tag2>

  <tag2>foo</tag2>
  <tag3>baz</tag3>
 </tag1>

Perl 스크립트는 입력 파일의 각 줄을 처리하고

  • if(/<tag1>/){$a=1;}: 여는 태그( )가 발견되면 변수가 $a로 설정됩니다.1<tag1>

  • if($a==1){push @l,$_}: 각 행에 대해, 그렇다면 $a해당 1행을 배열에 추가합니다 @l.

  • if(/<\/tag1>/): 현재 줄이 닫는 태그와 일치하는 경우:

    • if(grep {/foo/} @l){print "@l"}: 배열에 포함된 행( @l과 사이의 행) 중 하나라도 문자열과 일치하면 인쇄되는 내용입니다.<tag1></tag1>foo@l
    • $a=0; @l=(): 목록( )을 지우고 다시 0으로 @l=()설정합니다 .$a

답변4

내 생각에 GNU awk를 사용하면 닫는 태그를 다음과 같이 처리할 수 있을 것 같습니다.기록 구분 기호예를 들어 알려진 닫는 태그의 경우 다음과 같습니다 </tag1>.

gawk -vRS="\n</tag1>\n" '/foo/ {printf "%s%s", $0, RT}'

또는 더 일반적으로 (정규식을 닫는 태그로 사용)

gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}'

@terdon에서 테스트해 보세요 foo.xml.

$ gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}' foo.xml
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>

관련 정보