정규식을 사용하여 bash에서 검색 및 바꾸기


163

이 예를 보았습니다.

hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//[0-9]/}

다음 구문을 따릅니다. ${variable//pattern/replacement}

불행히도 pattern필드는 전체 정규식 구문을 지원하지 않는 것 같습니다 ( 예를 들어 .또는을 사용하는 경우 \s리터럴 문자와 일치하려고 시도합니다).

전체 정규식 구문을 사용하여 문자열을 검색 / 바꾸려면 어떻게해야합니까?


: 여기에 관련된 질문을 찾을 수 stackoverflow.com/questions/5658085/...
jheddings

2
참고 \s로 표준 POSIX 정의 정규식 구문 (BRE 또는 ERE 아님)의 일부가 아닙니다. PCRE 확장이며 대부분 셸에서 사용할 수 없습니다. [[:space:]]더 보편적 인 것입니다.
Charles Duffy 2014

1
\s로 대체 될 수있다 [[:space:]], 그런데, .에 의해 ?, 그리고베이스 라인 쉘 패턴 언어에 extglob 확장 등 옵션 하위 그룹, 반복 그룹 같은 것들에 사용 될 수 있습니다.
Charles Duffy


솔라리스의 bash 버전 4.1.11에서 이것을 사용합니다. echo $ {hello // [0-9]} 마지막 슬래시가 없습니다.
Daniel Liston

답변:


180

sed 사용 :

MYVAR=ho02123ware38384you443d34o3434ingtod38384day
echo "$MYVAR" | sed -e 's/[a-zA-Z]/X/g' -e 's/[0-9]/N/g'
# prints XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

후속 작업 -e은 순서대로 처리됩니다. 또한 g표현식 의 플래그는 입력의 모든 항목과 일치합니다.

이 방법을 사용하여 좋아하는 도구를 선택할 수도 있습니다 (예 : perl, awk).

echo "$MYVAR" | perl -pe 's/[a-zA-Z]/X/g and s/[0-9]/N/g'

이렇게하면 더 창의적인 일치를 수행 할 수 있습니다 ... 예를 들어 위의 캡처에서 첫 번째 표현식에 일치하는 항목이 없으면 숫자 대체가 사용되지 않습니다 (지연 and평가 로 인해 ). 그리고 물론, 당신은 당신의 입찰을 위해 Perl의 완전한 언어 지원을 가지고 있습니다.


이것은 내가 말할 수있는 한 단일 교체 만 수행합니다. 내가 게시 한 코드가 수행하는 것과 같은 패턴의 모든 발생을 대체하는 방법이 있습니까?
Lanaru

전역 패턴 일치뿐만 아니라 여러 교체를 보여주기 위해 내 대답을 업데이트했습니다. 도움이되는지 알려주세요.
jheddings

정말 고마워! 호기심에서 왜 한 줄 버전 (원래 답변에서)에서 두 줄 버전으로 전환 했습니까?
Lanaru

11
사용 sed또는 기타 외부 도구 인해 프로세스 초기화 시간이 비싸다. 특히 bash 대체를 사용하는 것이 sed루프의 각 항목을 호출하는 것보다 3 배 이상 빠르다는 것을 알았 기 때문에 모든 bash 솔루션을 검색했습니다 .
rr-

6
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视, 당연히, 그것은 일반적인 지혜이지만 그것이 현명하게 만들지 않습니다. 예, bash는 어떤 일이 있어도 느립니다.하지만 서브 쉘을 피하는 잘 작성된 bash는 모든 작은 작업에 대해 외부 도구를 호출하는 bash보다 문자 그대로 훨씬 빠릅니다. 또한 잘 작성된 쉘 스크립트는 더 빠른 인터프리터 (예 : awk와 동등한 성능을 가진 ksh93)의 이점을 얻을 수있는 반면 잘못 작성된 쉘 스크립트는 수행 할 작업이 없습니다.
Charles Duffy

137

이것은 실제로 순수한 bash에서 수행 할 수 있습니다.

hello=ho02123ware38384you443d34o3434ingtod38384day
re='(.*)[0-9]+(.*)'
while [[ $hello =~ $re ]]; do
  hello=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
echo "$hello"

... 수율 ...

howareyoudoingtodday

2
뭔가 당신이 이것을 좋아할 것이라고 말해줍니다 : stackoverflow.com/questions/5624969/… =)
nickl-

=~열쇠입니다. 그러나 루프의 재 할당을 고려할 때 약간 투박합니다. 2 년 전 @jheddings 솔루션은 sed 또는 perl을 호출하는 또 다른 좋은 옵션입니다.
Brent Faust

3
한 줄 이상의 입력을 처리하기 위해 각 호출을 사용하는 경우 sed또는 호출 perl이 합리적입니다. 루프를 사용하여 출력 스트림을 처리하는 것과 달리 루프 내부에서 이러한 도구를 호출하는 것은 어리석은 일입니다.
Charles Duffy

2
참고로, zsh을, 그것은 단지 $match대신 $BASH_REMATCH. (를 사용하여 bash처럼 동작하도록 할 수 있습니다 setopt bash_rematch.)
Marian

이상합니다 .zsh가 POSIX 셸이 되려고하지 않는 한, POSIX 지정 (셸 또는 시스템 관련) 목적으로 사용되는 모든 대문자 변수와 예약 된 소문자 변수에 대한 POSIX 지침의 문자를 따르고 있습니다. 응용 프로그램 사용. 그러나 zsh가 응용 프로그램 자체가 아니라 응용 프로그램 을 실행 하는 것이므로 시스템 이름 공간 대신 ​​응용 프로그램 변수 이름 공간을 사용하려는이 결정은 끔찍하게 왜곡 된 것 같습니다.
Charles Duffy

99

이 예제는 sed를 사용할 필요없이 bash에서도 작동합니다.

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[a-zA-Z]/X} 
echo ${MYVAR//[0-9]/N}

문자 클래스 대괄호 표현식을 사용할 수도 있습니다.

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[[:alpha:]]/X} 
echo ${MYVAR//[[:digit:]]/N}

산출

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

그러나 @Lanaru가 알고 싶었던 것은 내가 질문을 올바르게 이해했다면 "전체"또는 PCRE 확장이 왜 필요한지입니다. \s\S\w\W\d\D 등이 php ruby ​​python 등에서 지원 작동하지 않는 . 이러한 확장은 Perl 호환 정규식 (PCRE)에서 가져온 것입니다. 다른 형태의 쉘 기반 정규식과 호환되지 않을 수 있습니다.

작동하지 않습니다.

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//\d/}


#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | sed 's/\d//g'

모든 리터럴 "d"문자가 제거 된 출력

ho02123ware38384you44334o3434ingto38384ay

하지만 다음은 예상대로 작동합니다.

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | perl -pe 's/\d//g'

산출

howareyoudoingtodday

좀 더 명확 해지기를 바라지 만 아직 혼란스럽지 않다면 REG_ENHANCED 플래그가 활성화 된 Mac OS X에서이 작업을 시도해보십시오.

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day;
echo $MYVAR | grep -o -E '\d'

대부분의 * nix 버전에서는 다음 출력 만 표시됩니다.

d
d
d

nJoy!


6
용서? POSIX.2 BRE 또는 ERE 구문 ${foo//$bar/$baz}아닙니다. fnmatch () 스타일의 패턴 일치입니다.
Charles Duffy 2014 년

8
... 그래서, 반면에 ${hello//[[:digit:]]/}우리가 필터링 만 문자 앞에 자리를 원한다면 작품, o, ${hello//o[[:digit:]]*}, fnmatch에서 파생 된 패턴 이후 (예상보다 완전히 다른 행동을 할 것이다는 *오히려 일하기 직전 항목을 수정하는 것보다, 모든 문자와 일치 0 이상).
Charles Duffy

1
fnmatch에 대한 전체 사양 은 pubs.opengroup.org/onlinepubs/9699919799/utilities/… (및 참조로 통합 된 모든 항목)를 참조하십시오 .
Charles Duffy

1
man bash : == 및! =와 동일한 우선 순위로 추가 이진 연산자 = ~를 사용할 수 있습니다. 사용되는 경우 연산자 오른쪽에있는 문자열은 확장 정규식으로 간주되며 그에 따라 일치합니다 (regex (3)에서와 같이).
nickl- 2014 년

1
@aderchox 맞습니다. 사용할 수있는 숫자 [0-9]또는[[:digit:]]
nickl-

13

반복적 인 호출을하고 성능에 관심이있는 경우이 테스트는 BASH 메서드가 sed 및 다른 외부 프로세스보다 15 배 더 빠르다는 것을 보여줍니다.

hello=123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X

P1=$(date +%s)

for i in {1..10000}
do
   echo $hello | sed s/X//g > /dev/null
done

P2=$(date +%s)
echo $[$P2-$P1]

for i in {1..10000}
do
   echo ${hello//X/} > /dev/null
done

P3=$(date +%s)
echo $[$P3-$P2]

1
포크를 줄이는 방법에 관심이 있다면 Bash의 명령 출력에 변수를 설정하는 방법에 대한이 답변 에서 newConnector 라는 단어를 검색하십시오 .
F. Hauri


4

나는 이것이 고대의 스레드라는 것을 알고 있지만, 그것은 Google에서 처음으로 히트 한 것이었고 resub, 여러 $ 1, $ 2 등에 대한 지원을 추가 하는 다음 을 함께 공유하고 싶었습니다 .

#!/usr/bin/env bash

############################################
###  resub - regex substitution in bash  ###
############################################

resub() {
    local match="$1" subst="$2" tmp

    if [[ -z $match ]]; then
        echo "Usage: echo \"some text\" | resub '(.*) (.*)' '\$2 me \${1}time'" >&2
        return 1
    fi

    ### First, convert "$1" to "$BASH_REMATCH[1]" and 'single-quote' for later eval-ing...

    ### Utility function to 'single-quote' a list of strings
    squot() { local a=(); for i in "$@"; do a+=( $(echo \'${i//\'/\'\"\'\"\'}\' )); done; echo "${a[@]}"; }

    tmp=""
    while [[ $subst =~ (.*)\${([0-9]+)}(.*) ]] || [[ $subst =~ (.*)\$([0-9]+)(.*) ]]; do
        tmp="\${BASH_REMATCH[${BASH_REMATCH[2]}]}$(squot "${BASH_REMATCH[3]}")${tmp}"
        subst="${BASH_REMATCH[1]}"
    done
    subst="$(squot "${subst}")${tmp}"

    ### Now start (globally) substituting

    tmp=""
    while read line; do
        counter=0
        while [[ $line =~ $match(.*) ]]; do
            eval tmp='"${tmp}${line%${BASH_REMATCH[0]}}"'"${subst}"
            line="${BASH_REMATCH[$(( ${#BASH_REMATCH[@]} - 1 ))]}"
        done
        echo "${tmp}${line}"
    done
}

resub "$@"

##################
###  EXAMPLES  ###
##################

###  % echo "The quick brown fox jumps quickly over the lazy dog" | resub quick slow
###    The slow brown fox jumps slowly over the lazy dog

###  % echo "The quick brown fox jumps quickly over the lazy dog" | resub 'quick ([^ ]+) fox' 'slow $1 sheep'
###    The slow brown sheep jumps quickly over the lazy dog

###  % animal="sheep"
###  % echo "The quick brown fox 'jumps' quickly over the \"lazy\" \$dog" | resub 'quick ([^ ]+) fox' "\"\$low\" \${1} '$animal'"
###    The "$low" brown 'sheep' 'jumps' quickly over the "lazy" $dog

###  % echo "one two three four five" | resub "one ([^ ]+) three ([^ ]+) five" 'one $2 three $1 five'
###    one four three two five

###  % echo "one two one four five" | resub "one ([^ ]+) " 'XXX $1 '
###    XXX two XXX four five

###  % echo "one two three four five one six three seven eight" | resub "one ([^ ]+) three ([^ ]+) " 'XXX $1 YYY $2 '
###    XXX two YYY four five XXX six YYY seven eight

@Charles Duffy 에게 H / T 다시 :(.*)$match(.*)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.