Grep RegEx에서 그룹 캡처


380

sh(Mac OSX 10.6) 에이 작은 스크립트가있어 파일 배열을 살펴 봅니다. 이 시점에서 Google의 도움이 중단되었습니다.

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

지금까지 (분명히 쉘 전문가들에게) 파일 이름이 제공된 문제와 일치 $name하는지 여부에 따라 단순히 0, 1 또는 2를 보유 grep합니다. 내가 원하는 것은 parens 안에있는 것을 캡처 ([a-z]+)하여 변수에 저장하는 것 입니다.

가능한 경우에만 사용grep 하고 싶습니다 . 그렇지 않다면, Python이나 Perl 등을 피하십시오. sed저는 쉘을 처음 접했고 * nix 순수 주의자 각도에서 이것을 공격하고 싶습니다.

또한, 매우 멋진 bonu 로서 쉘에서 문자열을 어떻게 연결할 수 있는지 궁금합니다. 내가 캡처 한 그룹이 $ name에 저장된 문자열 "somename" cat $name '.jpg'입니까 , 끝에 ".jpg"문자열을 추가하고 싶 습니까?

시간이 있다면 무슨 일이 일어나고 있는지 설명하십시오.


30
그렙가 정말 나오지보다 유닉스 순수한?
클레이튼 마틴

3
아, 그런 제안을하지 않았습니다. 나는 여기서 특별히 배우려고하는 도구를 사용하여 솔루션을 찾을 수 있기를 바랐습니다. 그것을 사용하여 해결 할 수없는 경우 grep, 다음 sed그것을 사용하여 해결할 수 있다면, 좋은 것입니다 sed.
Isaac

2
나는 BTW 그에 ... :)을 했어야
클레이 턴 마틴을

Psh, 내 두뇌는 오늘 너무 튀겨
Isaac

2
@martinclayton 흥미로운 주장이 될 것입니다. grep이 ed 식 g (lobal) / re (gular expression) / p (rint)에서 이름을 얻었 기 때문에 sed, 또는 정확한 것으로 sed는 더 오래되고 따라서 더 순수합니까?
ffledgling

답변:


499

Bash를 사용하는 경우 grep다음 을 사용할 필요조차 없습니다 .

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files    # unquoted in order to allow the glob to expand
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

정규식을 변수에 넣는 것이 좋습니다. 문자 그대로 포함 된 일부 패턴은 작동하지 않습니다.

이것은 =~Bash의 정규식 일치 연산자를 사용합니다. 일치 결과는이라는 배열에 저장됩니다 $BASH_REMATCH. 첫 번째 캡처 그룹은 인덱스 1에 저장되고 두 번째 (있는 경우) 인덱스 2에 저장됩니다. 인덱스 0은 전체 일치입니다.

앵커가 없으면이 정규 표현식 (및을 사용하는 정규 표현식 grep)은 다음 예제 중 하나 이상과 일치하므로 원하는 것이 아닐 수도 있습니다.

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

두 번째와 네 번째 예를 제거하려면 정규식을 다음과 같이 만드십시오.

^[0-9]+_([a-z]+)_[0-9a-z]*

문자열은 하나 이상의 숫자로 시작 해야합니다 . 캐럿은 문자열의 시작을 나타냅니다. 정규식 끝에 달러 기호를 추가하면 다음과 같이됩니다.

^[0-9]+_([a-z]+)_[0-9a-z]*$

점이 정규식의 문자에 포함되지 않고 달러 기호가 문자열의 끝을 나타 내기 때문에 세 번째 예제도 제거됩니다. 네 번째 예제도이 일치에 실패합니다.

GNU를 가지고 있다면 grep(약 2.5 이상이면 \K연산자가 추가 된 것 같습니다.)

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

\K연산자 (가변 길이 모양 숨김)는 경기에 선행하는 패턴을 야기하지만, 결과에서 경기를 포함하지 않습니다. 고정 길이는 (?<=)-괄호 앞에 패턴이 포함됩니다. 당신은 사용해야합니다 \K한정사가 서로 다른 길이의 문자열을 일치 할 수있는 경우 (예를 들어 +, *, {2,4}).

(?=)연산자는 고정 길이 또는 가변 길이 패턴과 일치하며 "look-ahead"라고합니다. 또한 결과에 일치하는 문자열이 포함되지 않습니다.

대소 문자를 구분하지 않고 일치시키기 위해 (?i)연산자가 사용됩니다. 그것은 패턴을 따라가므로 위치가 중요합니다.

파일 이름에 다른 문자가 있는지 여부에 따라 정규식을 조정해야 할 수도 있습니다. 이 경우 하위 문자열을 캡처하는 동시에 문자열을 연결하는 예를 보여줍니다.


48
이 답변에서는 "정규식을 변수에 넣는 것이 좋습니다. 문자 그대로 포함하면 일부 패턴이 작동하지 않습니다."라는 특정 줄을 상향 조정하려고합니다.
Brandin

5
@FrancescoFrassinelli : 예는 공백을 포함하는 패턴입니다. 탈출하기가 어색하고 따옴표를 사용하면 정규 표현식에서 일반 문자열로 따옴표를 사용할 수 없으므로 따옴표를 사용할 수 없습니다. 올바른 방법은 변수를 사용하는 것입니다. 과제를하는 동안 따옴표를 사용하면 일이 훨씬 간단 해집니다.
추후 공지가있을 때까지 일시 중지되었습니다.

5
/K운영자 바위.
razz December

2
@Brandon : 작동합니다. 어떤 버전의 Bash를 사용하고 있습니까? 당신이하고있는 일이 효과가 없으며 왜 그런지 말해 줄 수 있습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

2
@ mdelolmo : 내 대답에는에 대한 정보가 포함되어 있습니다 grep. 그것은 또한 OP에 의해 받아 들여졌고 꽤 많이 찬성했습니다. downvote 주셔서 감사합니다.
추후 공지가있을 때까지 일시 중지되었습니다.

145

grep적어도 일반적으로 순수하지는 않지만 실제로는 불가능합니다 .

그러나 패턴이 적합한 경우 grep파이프 라인 내에서 여러 번 사용 하여 선을 알려진 형식으로 줄인 다음 원하는 비트 만 추출 할 수 있습니다. (이 도구 는이 도구를 좋아 cut하고 sed훨씬 나아집니다).

패턴이 조금 더 단순하다는 주장을 [0-9]+_([a-z]+)_위해 다음과 같이 추출 할 수 있습니다.

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

첫 번째 grep는 전체 patern과 일치하지 않는 행을 제거하고 두 번째 grep( --only-matching지정한)는 이름의 알파 부분을 표시합니다. 패턴이 적합하기 때문에 작동합니다. "알파 부분"은 원하는 것을 끌어낼 수있을만큼 구체적입니다.

(제외 : 개인적으로 grep+ cut를 사용 하여 다음을 달성 할 것입니다 : echo $name | grep {pattern} | cut -d _ -f 2. cut구분 기호로 분할하여 행을 필드로 구문 분석하고 _필드 2 만 반환합니다 (필드 번호는 1에서 시작합니다).

유닉스 철학은 한 가지 일을하고 잘 수행하는 도구를 가지고 사소하지 않은 작업을 수행하기 위해 도구를 결합하는 것이므로 grep+ sed등이 일을하는 더 Unixy 방법 이라고 주장합니다 :-)


3
for f in $files; do name=에코 $ f | grep -oEi '[0-9] + _ ([az] +) _ [0-9a-z] *'| 컷 -d _ -f 2 ;아하!
Isaac

2
나는 그 "철학"에 동의하지 않습니다. 외부 명령을 호출하지 않고 셸의 내장 기능을 사용할 수 있으면 스크립트 성능이 훨씬 빨라집니다. 기능이 겹치는 일부 도구가 있습니다. 예를 들어 grep, sed 및 awk. 그들 모두는 문자열 조작을 수행하지만 awk는 훨씬 더 많은 것을 할 수 있기 때문에 무엇보다 눈에.니다. 실제로 위의 더블 그렙이나 grep + sed와 같은 모든 명령 체인은 하나의 awk 프로세스로 수행함으로써 단축 될 수 있습니다.
ghostdog74 2009

7
@ ghostdog74 : 여기에서 많은 작은 작업을 연결하는 것이 일반적으로 한 곳에서 모든 작업을 수행하는 것보다 효율적이지 않다는 주장은 없지만 Unix 철학은 많은 도구가 함께 작동한다고 주장합니다. 예를 들어 tar는 파일을 보관하고 압축하지 않으며 기본적으로 STDOUT으로 출력하기 때문에 netcat을 사용하여 네트워크를 통해 파이프하거나 bzip2 등으로 압축 할 수 있습니다. 유닉스 도구는 파이프에서 함께 작동 할 수 있어야합니다.
RobM

컷은 대단합니다-팁 주셔서 감사합니다! 도구 대 효율성 주장은 체인 도구의 단순함을 좋아합니다.
ether_joe

grep의 o 옵션에 대한 소품은 매우 도움이됩니다
chiliNUT

96

나는 이것에 대한 답변이 이미 받아 들여 졌음을 알고 있지만 "엄격히 * nix 순수 주의자 각도"에서 그것은 작업에 대한 올바른 도구 인 pcregrep것처럼 보이지만 아직 언급되지 않은 것 같습니다. 줄을 바꾸어보십시오.

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

다음에

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

캡처 그룹 1의 내용 만 가져옵니다.

pcregrep도구는 이미 사용한 것과 동일한 구문을 모두 사용 grep하지만 필요한 기능을 구현합니다.

이 매개 변수 는 베어 버전 인 경우 버전 -o과 동일하게 작동 grep하지만에서 pcregrep표시 할 캡처 그룹을 나타내는 숫자 매개 변수도 허용합니다 .

이 솔루션을 사용하면 스크립트에서 최소한의 변경이 필요합니다. 하나의 모듈러 유틸리티를 다른 것으로 교체하고 매개 변수를 조정하면됩니다.

재미있는 참고 : 여러 개의 -o 인수를 사용하여 여러 캡처 그룹이 행에 나타나는 순서대로 반환 할 수 있습니다.


3
pcregrepMac OS XOP는 기본적으로 사용할 수 없습니다
grebneke

4
내는 pcregrep애프터 숫자 이해하지 않는 것 -o"-O1"알 수없는 옵션 문자 '1' "또한 그 functionaliy에 대한 언급이보고하지 않는 경우.pcregrep --help
피터 Herdenborg

1
@WAF 죄송합니다. 댓글에 해당 정보를 포함시켜야한다고 생각합니다. 저는 Centos 6.5를 사용하고 있으며 pcregrep 버전은 매우 오래된 것 같습니다 7.8 2008-09-05.
Peter Herdenborg

2
예, 매우 도움이됩니다echo 'r123456 foo 2016-03-17' | pcregrep -o1 'r([0-9]+)' 123456
zhuguowei

5
pcregrep8.41 ( apt-get install pcregrepon 과 함께 설치 Ubuntu 16.03)에서 -Ei스위치를 인식하지 못합니다 . 그럼에도 불구하고 완벽하게 작동합니다. @anishpatel이 언급 한 것처럼 (또한 8.41)을 pcregrep통해 설치된 macOS에서는 homebrew적어도 High Sierra에서는 -E스위치도 인식되지 않습니다.
Ville

27

내가 믿는 grep으로는 불가능

sed의 경우 :

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

그래도 보너스를 찌를 것입니다.

echo "$name.jpg"

2
불행히도 그 sed솔루션은 작동하지 않습니다. 단순히 내 디렉토리의 모든 것을 인쇄합니다.
Isaac

일치하지 않는 경우 업데이트, 빈 줄을 출력하므로 확인하십시오
cobbal

이제 빈 줄만 출력합니다!
Isaac

이 sed에 문제가 있습니다. 캡처 괄호의 첫 번째 그룹은 모든 것을 포함합니다. 물론 \ 2에는 아무것도 없습니다.
ghostdog74 2009

그것은 2 내부 그룹 얻는다 \ ... 몇 가지 간단한 테스트 케이스 근무
cobbal

16

gawk를 사용하는 솔루션입니다. 자주 사용해야하는 기능이므로 만들었습니다.

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

그냥 사용하기

$ echo 'hello world' | regex1 'hello\s(.*)'
world

좋은 생각이지만 정규 표현식에서 공백으로 작동하지 않는 것 같습니다-로 대체해야합니다 \s. 고치는 방법을 알고 있습니까?
Adam Ryczkowski '

4

제안 사항-매개 변수 확장을 사용하여 마지막 밑줄에서 시작 부분과 마찬가지로 이름의 일부를 제거 할 수 있습니다.

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

그런 다음 name값을 갖습니다 abc.

Apple 개발자 문서를 참조 하고 '매개 변수 확장'을 검색 하십시오 .


([az] +)는 확인하지 않습니다.
ghostdog74 2009

@levislevis-사실이지만 OP의 의견에 따르면 필요한 작업을 수행합니다.
martin clayton

2

배쉬가 있다면 확장 글러브를 사용할 수 있습니다.

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

또는

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

흥미로운 것 같습니다. 그것에 약간의 설명을 추가해 주시겠습니까? 또는 당신이 너무 기울어 있다면, 그것을 설명하는 특히 통찰력있는 자료에 연결합니까? 감사!
Isaac
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.