2012-10-24 12 views
90

Widziałem ten przykład:Znajdź i zastąp w bash za pomocą wyrażeń regularnych

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

który następuje po tej składni: ${variable//pattern/replacement}

Niestety pole pattern nie wydają się potwierdzać pełną składnię regex (jeśli Używam . lub \s, na przykład, próbuje dopasować literalne znaki).

Jak wyszukiwać/zastępować ciąg znaków za pomocą pełnej składni regex?

+0

Znaleziono powiązane pytanie tutaj: http://stackoverflow.com/questions/5658085/bash-script-regular-expressions-how-to-find-and-replace-all-matches – jheddings

+1

FYI, '\ s' nie jest częścią standardowej składni wyrażeń regularnych POSIX (ani BRE, ani ERE); jest to rozszerzenie PCRE, a najczęściej niedostępne z powłoki. '[[: space:]]' jest bardziej uniwersalnym odpowiednikiem. –

+1

'\ s' może być zastąpione przez' [[: spacja:]] ', nawiasem mówiąc,' .' przez '?', A rozszerzenie rozszerzenia do podstawowego języka wzorca powłoki może być używane do takich rzeczy jak opcjonalne podgrupy, powtarzane grupy i tym podobne. –

Odpowiedz

101

Zastosowanie sed:

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

Należy zauważyć, że dalsze -e'S przetwarzane są w porządku. Ponadto flaga g dla wyrażenia będzie pasować do wszystkich wystąpień w danych wejściowych.

Można również wybrać swoje ulubione narzędzie za pomocą tej metody, czyli Perl, awk, np:

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

To może pozwolić, aby zrobić bardziej twórcze mecze ... Na przykład, w Snipie powyżej, Zastępowanie numeryczne nie będzie stosowane, chyba że w pierwszym wyrażeniu występuje dopasowanie (z powodu leniwej oceny and). I oczywiście, masz pełną obsługę języka Perl, aby zrobić swoje rozkazy ...

+0

To tylko jeden zamień, o ile mogę powiedzieć. Czy istnieje sposób na zastąpienie wszystkich wystąpień wzoru, jak to, co napisałem w tym kodzie? – Lanaru

+0

Zaktualizowałem swoją odpowiedź, aby zademonstrować wiele zamienników oraz globalne dopasowywanie wzorców. Jeśli to pomoże, to daj mi znać. – jheddings

+0

Dziękuję bardzo! Z ciekawości, dlaczego przełączyłeś się z wersji jednoliniowej (w oryginalnej odpowiedzi) na dwu-liniową? – Lanaru

52

Przykłady te również pracować w bash bez potrzeby korzystania sed:

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

można również użyć wspornik klasa znaków wyrażenia

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

wyjściowe

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX 

Co @Lanaru chcieli wiedzieć Howe ver, jeśli dobrze rozumiem pytanie, to dlaczego "pełne" lub rozszerzenia PCRE \s\S\w\W\d\D itp. nie działają jako obsługiwane w php ruby ​​python itp. Te rozszerzenia są z zgodnych z Perl wyrażeń regularnych (PCRE) i mogą nie być kompatybilne z inne formy wyrażeń regularnych w powłoce.

te nie działają:

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


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

wyjście ze wszystkich dosłownych znaków "d" usunięte

ho02123ware38384you44334o3434ingto38384ay 

ale następujący działa zgodnie z oczekiwaniami

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

wyjście

howareyoudoingtodday 

nadzieja, że ​​wyjaśnia rzeczy nieco więcej, ale jeśli nie są jeszcze mylić dlaczego nie spróbujesz to na Mac OS X, który ma REG_ENHANCED flagę włączona:

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

Na większości smaków * nix będzie pan zobacz tylko następujące wyjście:

d 
d 
d 

nJoy!

+5

Pardon? '$ {foo // $ bar/$ baz}' jest ** nie ** POSIX.2 Składnia BRE lub ERE - to fnmatch() - dopasowanie wzorców. –

+6

... więc, podczas gdy '$ {hello // [[: cyfra:]] /}' działa, jeśli chcemy odfiltrować tylko cyfry poprzedzone literą 'o',' $ {hello // o [[ : digit:]] *} "miałoby zupełnie inne zachowanie niż oczekiwane (ponieważ we wzorach fnmatch,' * 'pasuje do wszystkich znaków, zamiast modyfikować bezpośrednio poprzedni element jako 0 lub więcej). –

+1

Zobacz http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 (i wszystko, co zawiera odnośnik) na pełną specyfikację na fnmatch. –

95

To rzeczywiście może być zrobione w czystej bash:

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

... plony ...

howareyoudoingtodday 
+1

Coś mi mówi, pokochasz te: http://stackoverflow.com/questions/5624969/how-to-reference-captures-in-bash-regex-replacement#answer-22261643 =) –

+7

To jest po prostu całkowicie niepoczytalny, a niesamowity w tym samym czasie ... – wich

+5

BASH_REMATCH jest niesamowity! Dzięki za wskazanie tego! – ricovox

2

Wpisz [[:digit:]] (nota podwójnych nawiasów) jako wzorca:

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

Po prostu chciałem podsumować odpowiedzi (szczególnie @ nickl-'s https://stackoverflow.com/a/22261334/2916086).

3

Jeśli wykonujesz wielokrotne połączenia i zależy Ci na wydajności, test ten ujawnia, że ​​metoda BASH jest ~ 15x szybsza niż rozwidlenie dla sed i prawdopodobnie jakikolwiek inny proces zewnętrzny.

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] 
Powiązane problemy