정규식으로 파일에서 여러 줄을 얻는 방법?
나는 종종 여러 줄을 얻거나 정규 표현식으로 여러 줄을 수정하고 싶습니다. 사례 :
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
오래된 공간입니다. 제가 보여줄 수있는 몇 가지 핵심 개념이 있습니다. 그래서 마지막 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
, 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>