Wyszukaj i zamień w bash używając wyrażeń regularnych


161

Widziałem ten przykład:

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

Co jest zgodne z następującą składnią: ${variable//pattern/replacement}

Niestety patternwydaje się, że pole nie obsługuje pełnej składni wyrażenia regularnego (jeśli używam .lub \s, na przykład, próbuje dopasować znaki literału).

Jak mogę wyszukać / zamienić ciąg przy użyciu pełnej składni wyrażeń regularnych?


Tutaj znalazłem powiązane pytanie: stackoverflow.com/questions/5658085/…
jheddings

2
FYI, \snie jest częścią standardowej składni wyrażeń regularnych zdefiniowanej w POSIX (ani BRE ani ERE); jest to rozszerzenie PCRE i przeważnie niedostępne z powłoki. [[:space:]]jest bardziej uniwersalnym odpowiednikiem.
Charles Duffy,

1
\smożna zastąpić [[:space:]], nawiasem mówiąc, .przez ?, a rozszerzenia extglob do bazowego języka wzorców powłoki mogą być używane do takich rzeczy, jak opcjonalne podgrupy, grupy powtarzane i tym podobne.
Charles Duffy,


Używam tego w bash w wersji 4.1.11 na Solarisie ... echo $ {hello // [0-9]} Zwróć uwagę na brak końcowego ukośnika.
Daniel Liston,

Odpowiedzi:


175

Użyj sed :

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

Zwróć uwagę, że kolejne -esą przetwarzane w kolejności. Ponadto gflaga wyrażenia będzie pasować do wszystkich wystąpień w danych wejściowych.

Możesz także wybrać swoje ulubione narzędzie używając tej metody, czyli perl, awk, np:

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

Może to pozwolić na bardziej kreatywne dopasowania ... Na przykład w powyższym wycinku numeryczny zamiennik nie zostanie użyty, chyba że w pierwszym wyrażeniu wystąpi dopasowanie (z powodu leniwej andoceny). I oczywiście masz pełne wsparcie językowe Perla, aby spełnić twoje wymagania ...


O ile wiem, powoduje to tylko jedną wymianę. Czy istnieje sposób, aby zastąpić wszystkie wystąpienia wzorca, tak jak robi to opublikowany przeze mnie kod?
Lanaru

Zaktualizowałem moją odpowiedź, aby pokazać wiele zamienników, a także globalne dopasowanie do wzorców. Jeśli to pomoże, to daj mi znać.
jheddings

Dzięki wielkie! Z ciekawości, dlaczego przeszedłeś z wersji jednowierszowej (w swojej pierwotnej odpowiedzi) na wersję dwuwierszową?
Lanaru

9
Używanie sedlub innych narzędzi zewnętrznych jest kosztowne ze względu na czas inicjalizacji procesu. Szczególnie szukałem rozwiązania all-bash, ponieważ stwierdziłem, że używanie podstawień basha jest ponad 3x szybsze niż wywoływanie sedkażdego elementu w mojej pętli.
rr-

6
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视, przyznaję, taka jest powszechna mądrość, ale to nie czyni tego mądrym. Tak, bash jest powolny bez względu na wszystko - ale dobrze napisany bash, który unika podpowłok, jest dosłownie o rząd wielkości szybszy niż bash, który wywołuje zewnętrzne narzędzia dla każdego małego zadania. Ponadto dobrze napisane skrypty powłoki skorzystają na szybszych interpreterach (takich jak ksh93, który ma wydajność równą awk), podczas gdy słabo napisane skrypty nie mają nic do roboty.
Charles Duffy

133

W rzeczywistości można to zrobić w czystym bashu:

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

... daje ...

howareyoudoingtodday

2
Coś mi mówi, że ci się spodoba: stackoverflow.com/questions/5624969/… =)
nickl-

=~jest kluczem. Ale trochę niezgrabny, biorąc pod uwagę zmianę przypisania w pętli. Rozwiązanie @jheddings 2 lata wcześniej to kolejna dobra opcja - wywołanie sed lub perl).
Brent Faust

3
Wywołanie sedlub perljest rozsądne, jeśli używasz każdego wywołania do przetwarzania więcej niż jednej linii danych wejściowych. Wywoływanie takiego narzędzia wewnątrz pętli, w przeciwieństwie do używania pętli do przetwarzania jej strumienia wyjściowego, jest ryzykowne.
Charles Duffy,

2
FYI, w zsh, to po prostu $matchzamiast $BASH_REMATCH. (Możesz sprawić, by zachowywał się jak bash z setopt bash_rematch.)
Marian

To dziwne - ponieważ zsh nie próbuje być powłoką POSIX, prawdopodobnie postępuje zgodnie z literą wskazówek POSIX dotyczących zmiennych składających się z samych wielkich liter używanych do celów określonych w POSIX (związanych z powłoką lub systemem), a zmienne z małymi literami są zarezerwowane dla użycie aplikacji. Ale ponieważ zsh jest czymś, co uruchamia aplikacje, a nie samą aplikację, ta decyzja o użyciu przestrzeni nazw zmiennych aplikacji zamiast przestrzeni nazw systemu wydaje się okropnie przewrotna.
Charles Duffy

95

Te przykłady działają również w bash bez potrzeby używania seda:

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

możesz także użyć wyrażeń nawiasowych klasy znaków

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

wynik

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

@Lanaru chciał jednak wiedzieć, jeśli dobrze rozumiem pytanie, dlaczego "pełne" rozszerzenia lub rozszerzenia PCRE \s\S\w\W\d\Ditp. Nie działają tak, jak są obsługiwane w PHP Ruby Python itp. Te rozszerzenia pochodzą z wyrażeń regularnych zgodnych z Perlem (PCRE) i może nie być kompatybilny z innymi formami wyrażeń regularnych opartych na 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 z usuniętymi wszystkimi znakami literału „d”

ho02123ware38384you44334o3434ingto38384ay

ale poniższe działa zgodnie z oczekiwaniami

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

wynik

howareyoudoingtodday

Mam nadzieję, że to wyjaśnia sprawę nieco więcej, ale jeśli jeszcze nie jesteś zdezorientowany, dlaczego nie wypróbujesz tego na Mac OS X, który ma włączoną flagę REG_ENHANCED:

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

Na większości smaków * nix zobaczysz tylko następujące dane wyjściowe:

d
d
d

nJoy!


6
Pardon? nie${foo//$bar/$baz} jest składnią POSIX.2 BRE ani ERE - jest to dopasowywanie wzorców w stylu fnmatch ().
Charles Duffy,

8
... tak więc, podczas gdy ${hello//[[:digit:]]/}Works, gdybyśmy chcieli odfiltrować tylko cyfry poprzedzone literą o, ${hello//o[[:digit:]]*}zachowywałby się zupełnie inaczej niż oczekiwano (ponieważ we wzorcach fnmatch *dopasowuje wszystkie znaki, zamiast modyfikować pozycję bezpośrednio poprzedzającą 0 lub więcej).
Charles Duffy,

1
Zobacz pubs.opengroup.org/onlinepubs/9699919799/utilities/ ... (i wszystko, co zawiera przez odniesienie), aby uzyskać pełną specyfikację fnmatch.
Charles Duffy,

1
man bash: Dostępny jest dodatkowy operator binarny = ~, z takim samym priorytetem jak == i! =. Kiedy jest używany, ciąg po prawej stronie operatora jest traktowany jako rozszerzone wyrażenie regularne i odpowiednio dopasowywany (jak w regex (3)).
nickl-

1
@aderchox masz rację, dla cyfr możesz użyć [0-9]lub[[:digit:]]
nickl-

13

Jeśli wykonujesz powtarzające się wywołania i obawiasz się wydajności, ten test pokazuje, że metoda BASH jest ~ 15x szybsza niż rozwidlanie do seda i prawdopodobnie każdego innego procesu zewnętrznego.

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
Jeśli interesuje Cię sposób na zmniejszenie widełek, wyszukaj słowo newConnector w odpowiedzi na pytanie Jak ustawić zmienną na wyjściu polecenia w Bash?
F. Hauri

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.