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


10

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

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

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>

이 예에서?



@evilsoup 사실이지만, 제 질문은 XML / SGML 파일에 관한 것이 아니라 텍스트 파일에 관한 것입니다.
Den

답변:


7

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

이것은 OP의 예제 파일에서 실행될 때 내 시스템에 출력을 제공하지 않습니다.
terdon

나를 위해 작동합니다. +1. (?s)팁 주셔서 감사합니다
Nathan Wallace

@terdon, 어떤 GNU grep 버전을 실행하고 있습니까?
iruvar

(GNU grep) 2.14데비안에 @ 1_CR . OPs 예제를 그대로 복사하고 (최종 개행 만 추가) 실행 grep했지만 결과는 얻지 못했습니다.
terdon

1
@slm, 저는 pcre 6.6, RHEL의 GNU grep 2.5.1에 ​​있습니다. 플랫폼 grep -ozP대신에 시도해 보 grep -oP시겠습니까?
iruvar

3
#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스크립트를 작성하는 방법 입니다.


@slm 감사합니다! 당신은 정말 괜찮은 사람입니다.
mikeserv

고마워, 좋은 일, 3k로 매우 빠르게 상승, 다음으로 5k 8-)
slm

나도 몰라, @slm. 나는 여기서 학습이 점점 줄어들고 있음을 알기 시작했다. 생각 해봐야 겠어 지난 몇 주 동안 간신히 사이트에 왔습니다.
mikeserv

최소한 10k에 도달하십시오. 잠금 해제 할 가치가있는 모든 것이 그 수준에 있습니다. 계속 치핑하면 5k가 상당히 빨라질 것입니다.
slm

1
음, @slm-당신은 어쨌든 희귀 품종입니다. 그래도 여러 답변에 동의합니다. 그래서 일부 qs가 닫히면 버그가 발생합니다. 그러나 실제로는 거의 발생하지 않습니다. 다시 한번 감사드립니다.
mikeserv

2

파일이 예제처럼 단순한 경우 @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으로 설정하십시오 .

이것은 "foo"를 포함하는 <tag1>이 두 개 이상인 경우를 제외하고는 잘 작동합니다. 이 경우 첫 번째 <tag1> 시작에서 마지막 </ tag1> 끝까지 모든 것을 인쇄합니다.
Den

나는 3가 들어 내 대답의 예와 그것을 테스트 @den <tag1>foo하고 그것을 잘 작동합니다. 언제 실패합니까?
terdon

그것은 정규식을 사용하여 XML을 구문 분석하는 것이 너무 느낀다 :)
Braiam

1

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

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

설명

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

1

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

0

파일이 위에 표시된대로 정확하게 구조화되어 있으면 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>

고마워요 이것은 단지 예일 뿐이며 실제 상황은 예측할 수없는 것처럼 보입니다. ;-)
Den

1
그것은 foo가 포함 된 태그를 찾는 것이 아니라 단지 foo를 찾고 컨텍스트 라인을 표시하는 것입니다.
Nathan Wallace

@NathanWallace 예, OP가 요구 한 것과 정확히 일치합니다.이 답변은 질문에 주어진 경우에 완벽하게 작동합니다.
terdon

@terdon 그것은 질문이 묻는 것이 아닙니다. 인용구 : "어딘가에 foo가 들어 있으면 <tag1>을 읽고 싶습니다." 이 솔루션은 " 'foo'가 나타나는 위치에 관계없이 'foo'와 한 줄의 컨텍스트를 읽고 싶습니다." 당신의 논리에 따라이 질문에 똑같이 유효한 대답은입니다 tail -3 input_file.xml. 예,이 특정 예에서는 효과가 있지만 질문에 대한 유용한 답변은 아닙니다.
Nathan Wallace

@NathanWallace 내 요점은 OP가 구체적으로 유효한 XML 형식이 아니라고 말 했으므로 OP가 검색하는 문자열 주위에 N 줄을 인쇄하는 것으로 충분했을 수 있습니다. 사용 가능한 정보를 바탕으로이 답변은 충분했습니다.
terdon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.