it-swarm.com.de

Nicht gierig (ungern) Regex Matching in sed?

Ich versuche, sed zu verwenden, um URL-Zeilen zu bereinigen und nur die Domain zu extrahieren.

Also von:

http://www.suepearson.co.uk/product/174/71/3816/

Ich möchte:

http://www.suepearson.co.uk/

(entweder mit oder ohne Trainling-Schrägstrich, egal)

Ich habe versucht:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

und (dem nicht gierigen Quantor entkommen)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

aber ich kann nicht scheinen, den nicht gierigen Quantifizierer zum Arbeiten zu bringen, also endet es immer, den vollständigen String zusammenzubringen.

384
Joel

Weder grundlegender noch erweiterter Posix/GNU-Regex erkennt den nicht-gierigen Quantifizierer. Du brauchst einen späteren regulären Ausdruck. Glücklicherweise ist Perl Regex für diesen Kontext ziemlich einfach zu bekommen:

Perl -pe 's|(http://.*?/).*|\1|'
400
chaos

In diesem speziellen Fall können Sie die Arbeit erledigen, ohne einen nicht gierigen regulären Ausdruck zu verwenden.

Versuchen Sie dieses nicht gierige Regex [^/]* anstatt .*?:

sed 's|\(http://[^/]*/\).*|\1|g'
233
Gumbo

Bei sed implementiere ich normalerweise eine nicht gierige Suche, indem ich nach etwas anderem als dem Trennzeichen suche, bis das Trennzeichen:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

Ausgabe:

http://www.suon.co.uk

das ist:

  • nicht ausgeben -n
  • suchen, Muster abgleichen, ersetzen und drucken s/<pattern>/<replace>/p
  • verwenden Sie das Suchbefehlstrennzeichen ; anstelle von /, um die Eingabe von s;<pattern>;<replace>;p zu vereinfachen.
  • erinnere dich an die Übereinstimmung zwischen den Klammern \( ... \), später mit \1, \2 ...
  • übereinstimmung http://
  • gefolgt von irgendetwas in Klammern [], würde [ab/] entweder a oder b oder / bedeuten.
  • zuerst bedeutet ^ in []not, also gefolgt von etwas anderem als dem in []
  • also bedeutet [^/] alles außer dem Zeichen /
  • * Soll die vorherige Gruppe wiederholen, so dass [^/]* Zeichen außer / Bedeutet.
  • bisher bedeutet sed -n 's;\(http://[^/]*\) suchen und speichern http:// gefolgt von beliebigen Zeichen außer / und speichern, was Sie gefunden haben
  • wir möchten bis zum Ende der Domain suchen. Halten Sie also beim nächsten / an. Fügen Sie am Ende ein weiteres / hinzu: sed -n 's;\(http://[^/]*\)/', aber wir möchten den Rest der Zeile abgleichen Fügen Sie nach der Domain .* hinzu.
  • jetzt ist die in Gruppe 1 gespeicherte Übereinstimmung (\1) die Domäne. Ersetzen Sie daher die übereinstimmende Zeile durch die in Gruppe \1 gespeicherten Angaben und drucken Sie: sed -n 's;\(http://[^/]*\)/.*;\1;p'

Wenn Sie auch einen Backslash nach der Domain einfügen möchten, fügen Sie der Gruppe einen weiteren Backslash hinzu, um sich zu erinnern:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

ausgabe:

http://www.suon.co.uk/
113
stefanB

sed unterstützt den Operator "non greedy" nicht.

Sie müssen den Operator "[]" verwenden, um "/" von der Übereinstimmung auszuschließen.

sed 's,\(http://[^/]*\)/.*,\1,'

P.S. Es ist kein Backslash "/" erforderlich.

36
andcoz

Simulieren eines faulen (nicht gierigen) Quantifizierers in sed

Und alle anderen Regex-Aromen!

  1. Das erste Vorkommen eines Ausdrucks finden:

    • POSIX ERE (mit der Option -r)

      Regex:

      (EXPRESSION).*|.
      

      Sed:

      sed -r "s/(EXPRESSION).*|./\1/g" # Global `g` modifier should be on
      

      Beispiel (erste Ziffernfolge finden) Live-Demo:

      $ sed -r "s/([0-9]+).*|./\1/g" <<< "foo 12 bar 34"
      
      12
      

      Wie funktioniert es ?

      Dieser reguläre Ausdruck profitiert von einer Abwechslung |. Bei jeder Position sucht der Motor nach der ersten Seite der Abwechslung (unserem Ziel) und wenn diese nicht übereinstimmt, stimmt die zweite Seite der Abwechslung mit einem Punkt . Mit dem nächsten unmittelbaren Zeichen überein.

      enter image description here

      Da das globale Flag gesetzt ist, versucht die Engine, Zeichen für Zeichen bis zum Ende der Eingabezeichenfolge oder bis zu unserem Ziel weiter abzugleichen. Sobald die erste und einzige Erfassungsgruppe der linken Seite des Wechsels übereinstimmt (EXPRESSION), Wird auch der Rest der Zeile sofort verbraucht .*. Wir halten jetzt unseren Wert in der ersten Eroberungsgruppe.

    • POSIX BRE

      Regex:

      \(\(\(EXPRESSION\).*\)*.\)*
      

      Sed:

      sed "s/\(\(\(EXPRESSION\).*\)*.\)*/\3/"
      

      Beispiel (erste Ziffernfolge finden):

      $ sed "s/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/" <<< "foo 12 bar 34"
      
      12
      

      Dieser ist wie die ERE-Version, jedoch ohne Wechsel. Das ist alles. Bei jeder einzelnen Position versucht der Motor, eine Ziffer zu finden.

      enter image description here

      Wenn es gefunden wird, werden andere folgende Ziffern verbraucht und erfasst und der Rest der Zeile wird sofort abgeglichen, da * mehr oder null bedeutet und die zweite Erfassungsgruppe überspringt \(\([0-9]\{1,\}\).*\)* und kommt zu einem Punkt ., der einem einzelnen Zeichen entspricht, und dieser Vorgang wird fortgesetzt.

  2. Finden des ersten Auftretens eines begrenzten Ausdrucks:

    Dieser Ansatz stimmt mit dem allerersten Vorkommen einer Zeichenfolge überein, die abgegrenzt ist. Wir können es einen String-Block nennen.

    sed "s/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g"
    

    Eingabezeichenfolge:

    foobar start block #1 end barfoo start block #2 end
    

    -EDE: end

    -SDE: start

    $ sed "s/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g"
    

    Ausgabe:

    start block #1 end
    

    Der erste reguläre Ausdruck \(end\).* stimmt mit dem ersten Endbegrenzer end überein und erfasst ihn. Ersetzt alle Übereinstimmungen mit den zuletzt erfassten Zeichen, bei denen es sich um den Endbegrenzer handelt. Zu diesem Zeitpunkt lautet unsere Ausgabe: foobar start block #1 end.

    enter image description here

    Dann wird das Ergebnis an die zweite reguläre Ausdrücke \(\(start.*\)*.\)* übergeben, die der obigen POSIX BRE-Version entspricht. Es stimmt mit einem einzelnen Zeichen überein, wenn das Starttrennzeichen start nicht übereinstimmt, andernfalls stimmt es mit dem Starttrennzeichen überein und erfasst es und stimmt mit den restlichen Zeichen überein.

    enter image description here


Beantworten Sie Ihre Frage direkt

Bei Verwendung von Ansatz 2 (durch Trennzeichen getrennter Ausdruck) sollten Sie zwei geeignete Ausdrücke auswählen:

  • EDE: [^:/]\/

  • SDE: http:

Verwendungszweck:

$ sed "s/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/" <<< "http://www.suepearson.co.uk/product/174/71/3816/"

Ausgabe:

http://www.suepearson.co.uk/
28
revo

Nicht gierige Lösung für mehr als einen einzelnen Charakter

Dieser Thread ist wirklich alt, aber ich gehe davon aus, dass die Leute ihn noch brauchen. Nehmen wir an, Sie möchten alles bis zum ersten Auftreten von HELLO töten. Du kannst nicht sagen [^HELLO]...

Eine Nice-Lösung besteht also aus zwei Schritten, vorausgesetzt, Sie können ein eindeutiges Wort sparen, das Sie in der Eingabe nicht erwarten, beispielsweise top_sekrit.

In diesem Fall können wir:

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

Natürlich könnten Sie mit einer einfacheren Eingabe ein kleineres Wort oder sogar ein einzelnes Zeichen verwenden.

HTH!

21
ishahak

sed - nicht gieriges Matching von Christoph Sieghart

Der Trick, um nicht gierige Übereinstimmungen in sed zu erhalten, besteht darin, alle Zeichen mit Ausnahme desjenigen, der die Übereinstimmung beendet, abzugleichen. Ich weiß, ein Kinderspiel, aber ich habe wertvolle Minuten damit verschwendet und Shell-Skripte sollten schließlich schnell und einfach sein. Für den Fall, dass jemand anderes es braucht:

Gieriges Matching

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Nicht gieriges Matching

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar
16
gresolio

Dies kann mit cut erfolgen:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
16
Dee

eine andere Möglichkeit, Regex nicht zu verwenden, ist die Verwendung der Methode fields/delimiter, z

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"
9
ghostdog74

sed hat sicherlich seinen Platz, aber dies ist keiner von ihnen!

Wie Dee darauf hingewiesen hat: Verwenden Sie einfach cut. In diesem Fall ist es viel einfacher und viel sicherer. Hier ist ein Beispiel, in dem wir verschiedene Komponenten mithilfe der Bash-Syntax aus der URL extrahieren:

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
Host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

gibt Ihnen:

protocol = "http"
Host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

Wie Sie sehen, ist dies ein sehr viel flexiblerer Ansatz.

(alle Kredite an Dee)

5
peterh

Es gibt noch Hoffnung, dieses Problem mithilfe von GNU-Sed zu lösen. Trotzdem ist dies in einigen Fällen keine generische Lösung. Sie können "Schleifen" verwenden, um alle unnötigen Teile der Zeichenfolge wie folgt zu entfernen:

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r: Verwenden Sie erweiterten regulären Ausdruck (für + und nicht gekapselte Klammern)
  • ": loop": Definiert ein neues Label mit dem Namen "loop"
  • -e: füge Kommandos zu sed hinzu
  • "t loop": Springe zurück zur Bezeichnung "loop", wenn eine erfolgreiche Ersetzung stattgefunden hat

Das einzige Problem hierbei ist, dass das letzte Trennzeichen ('/') abgeschnitten wird. Wenn Sie es jedoch wirklich benötigen, können Sie es nach Beendigung der "Schleife" einfach zurücksetzen, indem Sie diesen zusätzlichen Befehl am Ende des vorherigen Befehls anhängen Befehlszeile:

-e "s,$,/,"
4
mTUX
sed 's|(http:\/\/[^\/]+\/).*|\1|'
3
Lucero

sed -E interpretiert reguläre Ausdrücke als erweiterte (moderne) reguläre Ausdrücke

Update: -E unter MacOS X, -r in GNU sed.

3
stepancheg

Versuchen Sie die Gruppierung, da Sie ausdrücklich angegeben haben, dass Sie sed (anstelle von Perl, cut usw.) verwenden möchten. Dies umgeht, dass der nicht gierige Bezeichner möglicherweise nicht erkannt wird. Die erste Gruppe ist das Protokoll (d. H. "Http: //", "https: //", "tcp: //" usw.). Die zweite Gruppe ist die Domain:

 Echo "http://www.suon.co.uk/product/1/7/3/" | sed "s | ^\(. * // \)\([^ /] * \). * $ |\1\2 |" 

Wenn Sie mit Gruppierung nicht vertraut sind, starten Sie hier .

2
BrianB

Auf diese Weise können Sie mithilfe von sed einen nicht-gierigen Abgleich von Zeichenfolgen mit mehreren Zeichen durchführen. Nehmen wir an, Sie möchten jeden foo...bar in <foo...bar> ändern, also zum Beispiel diese Eingabe:

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

sollte diese Ausgabe werden:

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

Dazu konvertieren Sie foo und bar in einzelne Zeichen und verwenden dann die Negation dieser Zeichen dazwischen:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

In obigem:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/g konvertiert { und } in Platzhalterzeichenfolgen, die in der Eingabe nicht vorhanden sind, sodass diese Zeichen zum Konvertieren von foo und bar zur Verfügung stehen. zu.
  2. s/foo/{/g; s/bar/}/g konvertiert foo und bar in { bzw. }
  3. s/{[^{}]*}/<&>/g führt die gewünschte Operation aus - Konvertierung von foo...bar in <foo...bar>
  4. s/}/bar/g; s/{/foo/g konvertiert { und } zurück in foo und bar.
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g konvertiert die Platzhalterzeichenfolgen zurück in ihre ursprünglichen Zeichen.

Beachten Sie, dass das oben Gesagte nicht davon abhängt, dass eine bestimmte Zeichenfolge in der Eingabe nicht vorhanden ist, da es solche Zeichenfolgen im ersten Schritt herstellt, und dass es auch nicht darauf ankommt, mit welchem ​​Auftreten einer bestimmten Regexp Sie übereinstimmen möchten, da Sie {[^{}]*} verwenden können. So oft wie nötig im Ausdruck, um die gewünschte tatsächliche Übereinstimmung zu isolieren, und/oder mit dem numerischen seds-Übereinstimmungsoperator, z nur das 2. Vorkommen ersetzen:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV
1
Ed Morton

Mir ist klar, dass dies ein alter Eintrag ist, aber vielleicht findet ihn jemand nützlich. Da der vollständige Domainname eine Gesamtlänge von 253 Zeichen nicht überschreiten darf, ersetzen Sie. * Durch.\{1, 255 \}.

1
Iain Henderson

Haben Sie diese Antwort noch nicht gesehen, so können Sie dies mit vi oder vim tun:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

Dies führt die vi:%s globale Ersetzung (das nachfolgende g), unterlässt es, einen Fehler auszulösen, wenn das Muster nicht gefunden wird (e), speichert dann die resultierenden Änderungen auf der Festplatte und beendet sich. Das &>/dev/null verhindert, dass die GUI kurz auf dem Bildschirm blinkt, was ärgerlich sein kann.

Ich benutze vi manchmal für sehr komplizierte reguläre Ausdrücke, weil (1) Perl ist tot (2) vim hat eine sehr fortgeschrittene Regex-Engine, und (3) ich bin bereits mit vi Regexen in meinen alltäglichen Bearbeitungsdokumenten vertraut.

0
Luke Davis
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

mach dir keine Sorgen, ich habe es in einem anderen Forum :)

0
Dee

Eine andere Sed-Version:

sed 's|/[:alphanum:].*||' file.txt

Es passt / gefolgt von einem alphanumerischen Zeichen (also keinem weiteren Schrägstrich) sowie den restlichen Zeichen bis zum Zeilenende. Danach ersetzt es es durch nichts (dh löscht es.)

0
sycamorex

sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1| funktioniert auch

0
GL2014

Folgendes können Sie mit einem zweistufigen Ansatz und awk tun:

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

Ausgabe: http://www.suepearson.co.uk

Ich hoffe, das hilft!

0
VINAY NAIR