Normal ifadeler kullanarak bash'de arama ve değiştirme


161

Bu örneği gördüm:

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

Bu sözdizimini takip eden: ${variable//pattern/replacement}

Ne yazık ki patternalan tam regex sözdizimini desteklemiyor gibi görünüyor ( .veya kullanırsam \s, örneğin gerçek karakterlerle eşleşmeye çalışır).

Tam regex sözdizimini kullanarak bir dizeyi nasıl arayabilir / değiştirebilirim?


Burada ilgili bir soru
bulduk

2
FYI, \sstandart POSIX tanımlı normal ifade sözdiziminin bir parçası değildir (ne BRE ne de ERE); bir PCRE uzantısıdır ve çoğunlukla kabuktan kullanılamaz. [[:space:]]daha evrensel bir eşdeğerdir.
Charles Duffy

1
\sile ikame edilmiş olabilir [[:space:]], bu arada, .tarafından ?ve taban çizgisi kabuk model bir dile extglob uzantıları gibi isteğe bağlı alt tekrarlanan grupları gibi şeyler için kullanılan ve edilebilir.
Charles Duffy


Bunu Solaris üzerinde 4.1.11 bash sürümünde kullanıyorum ... echo $ {hello // [0-9]} Son eğik çizginin eksikliğine dikkat edin.
Daniel Liston

Yanıtlar:


176

Sed kullanın :

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

Takip edenlerin -esırayla işlendiğine dikkat edin. Ayrıca, gifade bayrağı girdideki tüm oluşumlarla eşleşir.

Bu yöntemi kullanarak en sevdiğiniz aracı da seçebilirsiniz, örn. Perl, awk, örn:

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

Bu, daha yaratıcı eşleşmeler yapmanıza izin verebilir ... Örneğin, yukarıdaki alıntıda, ilk ifadede bir eşleşme (tembel anddeğerlendirme nedeniyle ) olmadığı sürece sayısal değiştirme kullanılmaz . Ve elbette, teklifinizi yapmak için Perl'in tam dil desteğine sahipsiniz ...


Bu sadece anlatabildiğim kadarıyla tek bir yerine geçer. Gönderdiğim kodun yaptığı gibi, desenin tüm tekrarlarını değiştirmenin bir yolu var mı?
Lanaru

Cevabımı, global kalıp eşleşmesinin yanı sıra birden fazla değişiklik göstermek için güncelledim. Bunun yardımcı olup olmadığını bana bildirin.
jheddings

Çok teşekkürler! Meraktan, neden tek satırlık bir versiyondan (orijinal cevabınızda) iki astarlıya geçtiniz?
Lanaru

9
Kullanılması sedveya diğer harici araçlar nedeniyle işlem başlatma süresi pahalıdır. Özellikle all-bash çözümünü aradım, çünkü bash ikamelerini kullanarak döngümdeki sedher bir öğeyi çağırmaktan 3 kat daha hızlı buldum.
rr-

6
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视, kabul, bu ortak bilgelik, ama bu akıllıca yapmaz. Evet, bash ne olursa olsun yavaştır - ancak alt kabuklardan kaçınan iyi yazılmış bash, kelimenin tam anlamıyla her küçük küçük görev için harici araçlar çağıran bash'den daha hızlı büyüklük sıralarıdır. Ayrıca, iyi yazılmış kabuk betikleri daha hızlı tercümanlardan (awk ile eşit performansa sahip ksh93 gibi) yararlanırken, kötü yazılmış olanlar için yapılacak hiçbir şey yoktur.
Charles Duffy

133

Bu aslında olabilir saf bash yapılabilir:

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

... verimleri ...

howareyoudoingtodday

2
Bir şey bana bunları seveceğinizi söylüyor: stackoverflow.com/questions/5624969/… =)
nickl-

=~Anahtar mı. Ancak, döngüdeki yeniden atama göz önüne alındığında, biraz tıknaz. @jheddings çözümü 2 yıl önce başka bir iyi seçenektir - sed veya perl çağrısı).
Brent Faust

3
Arayan sedya da perlgiriş tek bir hat daha fazla işlem için her çağırma kullanılıyorsa, anlamlı olmayabilir. Çıkış akışını işlemek için bir döngü kullanmak yerine, bir döngü içinde böyle bir aracı çağırmak saçmadır.
Charles Duffy

2
FYI, zsh olarak, bunun $matchyerine $BASH_REMATCH. (Bash gibi davranmasını sağlayabilirsiniz setopt bash_rematch.)
Marian

Garip - zsh bir POSIX kabuğu olmaya çalışmadığı için, POSIX tarafından belirtilen (kabuk veya sistemle ilgili) amaçlar için kullanılan tüm büyük harf değişkenleri ve küçük harf değişkenleri için ayrılmış POSIX mektubunu tartışıyor. uygulama kullanımı. Ancak zsh, bir uygulama yerine uygulamaları çalıştıran bir şey olduğu gibi, sistem ad alanı yerine uygulama değişkeni ad alanını kullanma kararı çok sapkın görünüyor.
Charles Duffy

95

Bu örnekler ayrıca sed kullanmaya gerek kalmadan bash'da çalışır:

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

karakter sınıfı köşeli ayraç ifadelerini de kullanabilirsiniz

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

çıktı

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

@Lanaru'nun bilmek istediği şey, ancak soruyu doğru anlarsam, neden "tam" veya PCRE uzantılarının \s\S\w\W\d\D vb. Php ruby ​​python vb. Gibi desteklenmez. Bu uzantılar Perl uyumlu düzenli ifadelerden (PCRE) ve kabuk tabanlı normal ifadelerin diğer biçimleriyle uyumlu olmayabilir.

Bunlar işe yaramıyor:

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


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

tüm "d" karakterleri kaldırılmış çıktı

ho02123ware38384you44334o3434ingto38384ay

ancak aşağıdakiler beklendiği gibi çalışıyor

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

çıktı

howareyoudoingtodday

Bazı şeyleri biraz daha açıklığa kavuşturmayı umuyoruz, ancak henüz karıştırılmadıysanız, bunu neden REG_ENHANCED bayrağı etkin olan Mac OS X'te denemiyorsunuz:

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

* Nix'in çoğu çeşidinde sadece aşağıdaki çıktıyı görürsünüz:

d
d
d

Njoy!


6
Pardon? ${foo//$bar/$baz}olduğu değil POSIX.2 BRE veya ERE sözdizimi - 's fnmatch () - tarzı desen eşleştirme.
Charles Duffy

8
... bu nedenle, ${hello//[[:digit:]]/}işler, yalnızca harfin önündeki rakamları filtrelemek istersek o, ${hello//o[[:digit:]]*}beklenenden tamamen farklı bir davranışa sahip olurdu (çünkü fnmatch desenlerinde, *hemen önceki öğeyi değiştirmek yerine tüm karakterlerle eşleşir. 0-veya-daha fazla).
Charles Duffy

1
Bkz pubs.opengroup.org/onlinepubs/9699919799/utilities/... fnmatch tam spec için (ve referans ile birleştirir bütün bu).
Charles Duffy

1
man bash: == ve! = ile aynı önceliğe sahip ek bir ikili operatör, = ~ kullanılabilir. Kullanıldığında, operatörün sağındaki dize genişletilmiş bir normal ifade olarak kabul edilir ve buna göre eşleştirilir (normal ifadede (3) olduğu gibi).
nickl-

1
@aderchox haklısınız, kullanabileceğiniz basamaklar için [0-9]veya[[:digit:]]
nickl-

13

Tekrarlanan çağrılar yapıyorsanız ve performansla ilgileniyorsanız, Bu test, BASH yönteminin sed'e ve muhtemelen diğer harici işlemlere çatallamadan ~ 15x daha hızlı olduğunu ortaya koymaktadır.

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

8

[[:digit:]]Desen olarak (çift parantezleri not edin) kullanın :

$ hello=ho02123ware38384you443d34o3434ingtod38384day
$ echo ${hello//[[:digit:]]/}
howareyoudoingtodday

Sadece cevapları özetlemek istedim (özellikle @ nickl- 'in https://stackoverflow.com/a/22261334/2916086 ).


1

Bunun eski bir iş parçacığı olduğunu biliyorum, ancak Google'daki ilk hitimdi ve bir resubaraya getirdiğim aşağıdakileri paylaşmak istedim , bu da birden fazla 1 $, 2 $ vb.

#!/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

H / T to @Charles Duffy re:(.*)$match(.*)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.