[unix] 정규식으로 파일에서 여러 줄을 얻는 방법?

정규식으로 파일에서 여러 줄을 얻는 방법?

나는 종종 여러 줄을 얻거나 정규 표현식으로 여러 줄을 수정하고 싶습니다. 사례 :

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>

이 예에서?



답변

GNU grep이 설치되어 있으면 -P(perl-regex) 플래그 를 전달하고 다음을 사용하여 여러 줄 검색을 수행 할 수 PCRE_DOTALL있습니다.(?s)

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

위의 작업이 플랫폼에서 작동하지 않으면 -z플래그를 추가로 시도하십시오. 그러면 grep이 NUL을 줄 구분자로 처리하여 전체 파일이 단일 줄처럼 보이게합니다.

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


답변

#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>$

당신이 원하는 때마다 패턴 공간을 인쇄 할 수 있습니다 l. 그런 다음 \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위해 l우리는 우리가 우리의 스크립트를 개발하고 비 연산 데모를 제거 할 때 함께 작업하는 것을 볼 수 있도록 s?우리의 마지막 줄 수 있도록 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, e는 한 번만 변경되므로 홀드 공간 패턴 공간이되고 그 반대도 마찬가지 입니다.

그 결과 패턴 공간이었던 홀드 공간을 삭제해야합니다. 먼저 다음을 사용하여 현재 패턴 공간을 지 웁니다.

s/.*//

단순히 모든 문자를 선택하고 제거합니다. d이것이 현재 라인 사이클을 끝내고 다음 명령이 완료 되지 않아 사용할 수 없기 때문에 스크립트가 거의 손상됩니다.

h

이것은 비슷한 방식으로 작동 H하지만 보류 공간을 덮어 쓰므로 보류 패턴의 상단에 빈 패턴 공간을 복사하여 효과적으로 삭제했습니다. 이제는 다음과 같이 할 수 있습니다.

b

밖.

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


답변

파일이 예제처럼 단순한 경우 @jamespfinn의 답변이 완벽하게 작동합니다. <tag1>두 줄 이상으로 확장 될 수 있는보다 복잡한 상황 인 경우 약간 더 복잡한 트릭이 필요합니다. 예를 들면 다음과 같습니다.

$ 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>

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

  • 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=(): 목록을 비우고 ( @l=()) $a다시 0으로 설정하십시오 .

답변

sed대안은 다음과 같습니다 .

sed -n '/<tag1/{:x N;/<\/tag1/!b x};/foo/p' your_file

설명

  • -n 지시가없는 한 줄을 인쇄하지 않음을 의미합니다.
  • /<tag1/ 먼저 여는 태그와 일치
  • :x 나중에이 지점으로 건너 뛸 수있는 레이블입니다.
  • N 패턴 공간에 다음 행을 추가합니다 (활성 버퍼).
  • /<\/tag1/!b x현재 패턴 공간에 닫는 태그가없는 경우 x이전에 작성된 레이블로 분기합니다 . 따라서 닫는 태그를 찾을 때까지 패턴 공간에 선을 계속 추가합니다.
  • /foo/p현재 패턴 공간이 일치 foo하면 인쇄되어야 함을 의미합니다 .

답변

종료 태그를 알려진 구분 태그와 같은 레코드 구분 기호 로 처리하여 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>


답변

파일이 위에 표시된대로 정확하게 구조화되어 있으면 grep에 -A (이후 라인) 및 -B (이전 라인) 플래그를 사용할 수 있습니다. 예를 들면 다음과 같습니다.

$ cat yourFile.txt
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
$ grep -A1 -B1 bar yourFile.txt
<tag1>
 <tag2>bar</tag2>
</tag1>
$ grep -A1 -B1 foo yourFile.txt
<tag1>
 <tag2>foo</tag2>
</tag1>

사용중인 버전에서 grep지원하는 -C경우 주변 N 줄을 인쇄하는 더 간단한 (컨텍스트 용) 옵션을 사용할 수도 있습니다 .

$ grep -C 1 bar yourFile.txt
<tag1>
 <tag2>bar</tag2>
</tag1>


답변