grep에서 일치하는 지정된 그룹 만 출력 할 수 있습니까?


290

파일이 있다고 가정 해보십시오.

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar

"foobar"뒤에 나오는 단어 만 알고 싶기 때문에이 정규식을 사용할 수 있습니다.

"foobar \(\w\+\)"

괄호는 내가 foobar 바로 뒤에있는 단어에 특별한 관심이 있음을 나타냅니다. 내가 할 때 grep "foobar \(\w\+\)" test.txt, 차라리 그냥 "는 foobar 후 단어"보다, 전체 정규 표현식과 일치하는 전체 라인을 얻을 :

foobar bash 1
foobar happy

해당 명령의 출력이 다음과 같이 보이는 것이 훨씬 좋습니다.

bash
happy

grep에게 그룹화 (또는 특정 그룹화)와 일치하는 항목 만 정규식으로 출력하도록 지시하는 방법이 있습니까?


4
grep이 필요없는 사람들을 위해 :perl -lne 'print $1 if /foobar (\w+)/' < test.txt
vault

답변:


325

GNU grep에는 -Pperl 스타일 -o정규식 옵션과 패턴과 일치하는 항목 만 인쇄 하는 옵션이 있습니다. 이것들은 둘러보기 어설 션 ( perlre 맨 페이지의 확장 패턴에 설명되어 있음)을 사용하여 조합하여 grep 패턴의 일부를 목적에 맞는 것으로 판단한 것에서 제거 할 수 있습니다 -o.

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

\K의 짧은 형태 (보다 효율적인 양식)입니다 (?<=pattern)당신이 출력 할 텍스트 전에 제로 폭 보이는 숨김 주장으로 사용한다. (?=pattern)출력하려는 ​​텍스트 다음에 폭이 0 인 미리보기 어설 션으로 사용할 수 있습니다.

당신의 말과 일치하기를 원한다면 예를 들어, foobar, 당신은 사용할 수 있습니다 :

$ grep -oP 'foo \K\w+(?= bar)' test.txt

또는 (대칭)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt

3
정규식에 그룹 이상이 있으면 어떻게합니까? (제목이 암시
한대로

4
@ barracel : 나는 당신이 할 수 있다고 생각하지 않습니다. 시간sed(1)
camh

1
@ camh 방금 grep -oP 'foobar \K\w+' test.txtOP의 아무것도 출력하지 않는지 테스트했습니다 test.txt. grep 버전은 2.5.1입니다. 무엇이 잘못 될 수 있습니까? O_O
SO 사용자

@XichenLi : 말할 수 없습니다. 나는 방금 v2.5.1 grep (2006 년부터 꽤 오래되었습니다)을 만들었고 저에게 효과적이었습니다.
camh

@SOUser : 나는 같은 것을 경험했다-파일에 아무것도 출력하지 않는다. 출력 파일을 보내기 위해 파일 이름 앞에 '>'를 포함하도록 편집 요청을 제출했습니다.
rjchicago

39

표준 grep은이 작업을 수행 할 수 없지만 최신 버전의 GNU grep 은이 작업을 수행 할 수 있습니다 . sed, awk 또는 perl로 설정할 수 있습니다. 다음은 샘플 입력에서 원하는 것을 수행하는 몇 가지 예입니다. 코너 케이스에서는 약간 다르게 동작합니다.

교체 foobar word other stuffword, 교체가 완료되는 경우에만 인쇄 할 수 있습니다.

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

첫 번째 단어가 foobar인 경우 두 번째 단어를 인쇄하십시오.

awk '$1 == "foobar" {print $2}'

foobar그것이 첫 단어라면 벗기고 그렇지 않으면 줄을 건너 뜁니다. 그런 다음 첫 공백 뒤에있는 모든 것을 제거하고 인쇄하십시오.

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'

대박! sed 로이 작업을 수행 할 수 있다고 생각했지만 이전에는 사용하지 않았고 익숙한을 사용할 수 있기를 바랐습니다 grep. 그러나이 명령의 구문은 vim 스타일 검색 및 바꾸기 + 정규 표현식에 익숙해 졌으므로 실제로 매우 친숙하게 보입니다. 엄청 고마워.
코리 클라인

1
사실이 아니야, 질 GNU grep 솔루션에 대한 내 대답을 참조하십시오.
camh

1
@ camh : 아, GNU grep이 PCRE를 완전히 지원한다는 것을 몰랐습니다. 답변을 수정했습니다. 감사합니다.
Gilles

1
Busybox grep는 PCRE를 지원하지 않기 때문에이 답변은 임베디드 Linux에 특히 유용합니다 .
Craig McQueen

OP가 grep 사용법을 요구하면 왜 다른 것에 대답해야합니까? 또한 첫 번째 단락이 잘못되었습니다. 예 grep이 할 수 있습니다.
fcm

32
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it

1
sed 예제의 경우 +1은 grep보다 작업에 더 적합한 도구 인 것 같습니다. 한 가지 의견 은 욕심이 일치하기 때문에 외부 ^와 관련 $이 없습니다 .*. 그러나 그것들을 포함 시키면 정규 표현식의 의도를 분명히하는 데 도움이 될 수 있습니다.
Tony

18

foobar가 항상 첫 단어 또는 줄이라는 것을 알고 있다면 cut을 사용할 수 있습니다. 이렇게 :

grep "foobar" test.file | cut -d" " -f2

-ogrep 의 스위치는 Gnu grep 확장보다 널리 구현되므로이 grep -o "foobar" test.file | cut -d" " -f2솔루션의 효율성이 향상되어 lookbehind 어설 션을 사용하는 것보다 이식성이 뛰어납니다.
dubiousjim

나는 당신이 필요하다고 믿습니다 . grep -o "foobar .*grep -o "foobar \w+"
G-Man

9

PCRE가 지원되지 않으면 grep을 두 번 호출하여 동일한 결과를 얻을 수 있습니다. 예를 들어 foobar 다음에 단어를 얻으려면 다음을 수행하십시오.

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

다음 과 같이 foobar 다음에 임의의 단어로 확장 할 수 있습니다 (가독성을 위해 ERE 사용).

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

산출:

1

인덱스 i는 0부터 시작합니다.


6

pcregrep-o출력하려는 ​​캡처 그룹을 선택할 수 있는 더 스마트 한 옵션이 있습니다. 예제 파일을 사용하면

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy

4

사용은 grep이후, 크로스 플랫폼 호환되지 않습니다 -P/이 --perl-regexp에서만 사용할 GNUgrep 하지 BSDgrep .

다음을 사용하는 솔루션이 있습니다 ripgrep.

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

에 따라 man rg:

-r/ --replace REPLACEMENT_TEXT모든 일치하는 텍스트를 주어진 텍스트로 바꿉니다.

캡처 그룹 인덱스 (예 :) $5및 이름 (예 :) $foo이 대체 문자열에서 지원됩니다.

관련 : GH-462 .


2

@jgshawkey의 답변이 매우 유용하다는 것을 알았습니다. grep여기에는 그리 좋은 도구는 아니지만 sed는 grep을 사용하여 관련 라인을 가져 오는 예제가 있지만 여기에는 sed가 있습니다.

sed의 정규식 구문은 익숙하지 않은 경우 특이합니다.

여기 또 다른 예가 있습니다 : 이것은 xinput의 출력을 분석하여 ID 정수를 얻습니다.

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

나는 19를 원한다

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

클래스 구문에 유의하십시오.

[[:digit:]]

그리고 다음을 피할 필요성 +

한 줄만 일치한다고 가정합니다.


이것이 바로 내가하려고했던 것입니다. 감사!
제임스

grep'터치 패드'가 'id'의 왼쪽에 있다고 가정하면 추가 기능이없는 약간 더 간단한 버전입니다 .echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
Amit Naidu
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.