[bash] Grep RegEx에서 그룹 캡처

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”문자열을 추가하고 싶 습니까?

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



답변

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)연산자가 사용됩니다. 그것은 패턴을 따라가므로 위치가 중요합니다.

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


답변

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 방법 이라고 주장합니다 🙂


답변

나는 이것에 대한 답변이 이미 받아 들여 졌음을 알고 있지만 “엄격히 * 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 인수를 사용하여 여러 캡처 그룹이 행에 나타나는 순서대로 반환 할 수 있습니다.


답변

내가 믿는 grep으로는 불가능

sed의 경우 :

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

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

echo "$name.jpg"


답변

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

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

그냥 사용하기

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


답변

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

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

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

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


답변

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

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