Suchen und Ersetzen in Bash mit regulären Ausdrücken

160

Ich habe dieses Beispiel gesehen:

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

Welches folgt dieser Syntax: ${variable//pattern/replacement}

Leider patternscheint das Feld keine vollständige Regex-Syntax zu unterstützen (wenn ich es verwende .oder \szum Beispiel versuche, mit den Literalzeichen übereinzustimmen).

Wie kann ich eine Zeichenfolge mit vollständiger Regex-Syntax suchen / ersetzen?

Lanaru
quelle
Hier finden Sie eine verwandte Frage: stackoverflow.com/questions/5658085/…
jheddings
2
Zu Ihrer Information, \sist nicht Teil der von POSIX definierten Standardsyntax für reguläre Ausdrücke (weder BRE noch ERE). Es ist eine PCRE-Erweiterung und meistens nicht über die Shell erhältlich. [[:space:]]ist das universellere Äquivalent.
Charles Duffy
1
\skann [[:space:]]übrigens .durch ersetzt werden ?, und extglob-Erweiterungen der Baseline-Shell-Mustersprache können für Dinge wie optionale Untergruppen, wiederholte Gruppen und dergleichen verwendet werden.
Charles Duffy
Ich verwende dies in der Bash-Version 4.1.11 unter Solaris ... echo $ {hello // [0-9]} Beachten Sie das Fehlen des letzten Schrägstrichs.
Daniel Liston

Antworten:

175

Verwenden Sie sed :

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

Beachten Sie, dass die nachfolgenden in -eder richtigen Reihenfolge verarbeitet werden. Das gFlag für den Ausdruck stimmt auch mit allen Vorkommen in der Eingabe überein.

Mit dieser Methode können Sie auch Ihr Lieblingswerkzeug auswählen, z. B. Perl, Awk, z.

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

Auf diese Weise können Sie möglicherweise mehr kreative Übereinstimmungen erstellen ... Im obigen Ausschnitt wird beispielsweise die numerische Ersetzung nur verwendet, wenn der erste Ausdruck eine Übereinstimmung aufweist (aufgrund von Faulheit) and Auswertung). Und natürlich haben Sie die volle Sprachunterstützung von Perl, um Ihre Gebote abzugeben ...

jheddings
quelle
Dies ersetzt nur einen einzigen, soweit ich das beurteilen kann. Gibt es eine Möglichkeit, alle Vorkommen des Musters zu ersetzen, wie es der von mir gepostete Code tut?
Lanaru
Ich habe meine Antwort aktualisiert, um mehrere Ersetzungen sowie den globalen Mustervergleich zu demonstrieren. Lassen Sie mich wissen, ob das hilft.
jheddings
Vielen Dank! Warum haben Sie aus Neugier von einer einzeiligen Version (in Ihrer ursprünglichen Antwort) zu einer zweizeiligen Version gewechselt?
Lanaru
9
Die Verwendung sedoder anderer externer Tools ist aufgrund der Prozessinitialisierungszeit teuer. Ich habe insbesondere nach einer All-Bash-Lösung gesucht, da ich festgestellt habe, dass die Verwendung von Bash-Substitutionen mehr als dreimal schneller ist als das Aufrufen sedjedes Elements in meiner Schleife.
rr-
6
@CiroSantilli granted 事件 法轮功 纳米比亚 威 视, zugegeben, das ist die allgemeine Weisheit, aber das macht es nicht weise. Ja, Bash ist langsam, egal was passiert - aber gut geschriebenes Bash, das Subshells vermeidet, ist buchstäblich um Größenordnungen schneller als Bash, das externe Tools für jede kleine Aufgabe aufruft. Gut geschriebene Shell-Skripte profitieren außerdem von schnelleren Interpreten (wie ksh93, dessen Leistung mit awk vergleichbar ist), während schlecht geschriebene Skripte nichts zu tun haben.
Charles Duffy
133

Dies kann tatsächlich in reiner Bash erfolgen:

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

... ergibt ...

howareyoudoingtodday
Charles Duffy
quelle
2
Etwas sagt mir, dass Sie diese lieben werden: stackoverflow.com/questions/5624969/… =)
nickl-
=~ist der Schlüssel. Aber etwas klobig angesichts der Neuzuweisung in der Schleife. @jheddings Lösung 2 Jahre zuvor ist eine weitere gute Option - Sed oder Perl anrufen).
Brent Faust
3
Ein Aufruf sedoder perlist sinnvoll, wenn mit jedem Aufruf mehr als eine einzelne Eingabezeile verarbeitet wird. Das Aufrufen eines solchen Tools im Inneren einer Schleife ist tollkühn, im Gegensatz zur Verwendung einer Schleife zur Verarbeitung ihres Ausgabestreams.
Charles Duffy
2
Zu Ihrer Information, in zsh ist es einfach $matchstatt $BASH_REMATCH. (Sie können es wie Bash mit verhalten setopt bash_rematch.)
Marian
Es ist seltsam - da zsh nicht versucht, eine POSIX-Shell zu sein, folgt es wohl dem Buchstaben der POSIX-Anleitung, dass All-Caps-Variablen für POSIX-spezifizierte (Shell- oder systemrelevante) Zwecke verwendet und Kleinbuchstaben-Variablen reserviert werden Anwendung verwenden. Aber da zsh ist etwas , das läuft Anwendungen, und nicht als eine Anwendung selbst, diese Entscheidung zu variablen Namensraum bedienende Anwendung , anstatt das System Namespace schrecklich perverse scheint.
Charles Duffy
94

Diese Beispiele funktionieren auch in Bash, ohne dass sed verwendet werden muss:

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

Sie können auch die Ausdrücke der Zeichenklassenklammer verwenden

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

Ausgabe

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

Was @Lanaru jedoch wissen wollte, wenn ich die Frage richtig verstehe, ist, warum die "Voll" - oder PCRE-Erweiterungen \s\S\w\W\d\Dusw. nicht so funktionieren, wie sie in PHP Ruby Python usw. unterstützt werden. Diese Erweiterungen stammen aus Perl-kompatiblen regulären Ausdrücken (PCRE) und ist möglicherweise nicht mit anderen Formen von Shell-basierten regulären Ausdrücken kompatibel.

Diese funktionieren nicht:

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


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

Ausgabe mit allen wörtlichen "d" -Zeichen entfernt

ho02123ware38384you44334o3434ingto38384ay

Das Folgende funktioniert jedoch wie erwartet

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

Ausgabe

howareyoudoingtodday

Ich hoffe, das klärt die Dinge ein bisschen mehr, aber wenn Sie noch nicht verwirrt sind, warum versuchen Sie dies nicht unter Mac OS X, für das das Flag REG_ENHANCED aktiviert ist:

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

Bei den meisten Varianten von * nix wird nur die folgende Ausgabe angezeigt:

d
d
d

nJoy!

Nickl-
quelle
6
Pardon? ${foo//$bar/$baz}ist keine POSIX.2 BRE- oder ERE-Syntax - es ist ein Mustervergleich im fnmatch () - Stil.
Charles Duffy
8
... so, während ${hello//[[:digit:]]/}Werke, wenn wir nur zum Ausfiltern von Ziffern mit vorangestelltem Buchstaben wollten o, ${hello//o[[:digit:]]*}hätte ein ganz anderes Verhalten als die erwarteten (da in fnmatch Mustern, *paßt auf alle Zeichen, anstatt die unmittelbar vor Punkt modifiziert werden 0 oder mehr).
Charles Duffy
1
Die vollständige Spezifikation von fnmatch finden Sie unter pubs.opengroup.org/onlinepubs/9699919799/utilities/… (und alles, was darin als Referenz enthalten ist).
Charles Duffy
1
man bash: Ein zusätzlicher binärer Operator = ~ ist verfügbar, mit der gleichen Priorität wie == und! =. Bei Verwendung wird die Zeichenfolge rechts vom Operator als erweiterter regulärer Ausdruck betrachtet und entsprechend abgeglichen (wie in Regex (3)).
Nickl
1
@aderchox Sie sind richtig, für Ziffern können Sie verwenden [0-9]oder[[:digit:]]
nickl-
13

Wenn Sie wiederholt Anrufe tätigen und sich um die Leistung kümmern, zeigt dieser Test, dass die BASH-Methode ~ 15x schneller ist als das Verzweigen in sed und wahrscheinlich jeder andere externe Prozess.

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]
Josiah DeWitt
quelle
1
Wenn Sie daran interessiert sind, Gabeln zu reduzieren, suchen Sie in dieser Antwort nach Wie man eine Variable auf die Ausgabe eines Befehls in Bash setzt
F. Hauri
8

Verwenden Sie [[:digit:]](beachten Sie die doppelten Klammern) als Muster:

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

Ich wollte nur die Antworten zusammenfassen (insbesondere @ nickl-s https://stackoverflow.com/a/22261334/2916086 ).

yegeniy
quelle