정규식을 통해 파일에서 여러 줄을 얻는 방법은 무엇입니까?
나는 종종 정규 표현식을 통해 여러 줄을 가져오거나 수정하고 싶습니다. 예시 사례:
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>
위의 방법이 플랫폼에서 작동하지 않으면 -z
grep이 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>$
l
ook를 사용하여 언제든지 패턴 공간을 인쇄 할 수 있습니다 . 그런 다음 \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
건축 당시의 오래된 공간입니다. 몇 가지 핵심 개념을 보여드리고 싶습니다. 그래서 다시 마지막 l
ook을 제거하고 첫 번째 줄을 변경하여 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>